平時開發中,Vue的響應式系統讓我們不再去操作DOM,只需關心數據邏輯的處理,極大地降低了代碼的復雜度。而響應式系統也是Vue的核心,作為開發者有必要了解其實現原理!
簡易版
以watch為切入點
watch是平時開發中使用率非常高的功能,其目的是觀測一個數據,當數據變化時執行我們預先定義的回調。使用方式如下:
{ watch: { obj(val, oldVal) { console.log(val, oldVal); } }}上面觀測了Vue實例的obj屬性,當其值發生變化時,打印出新值與舊值。
因此,我們定義一個watch函數:
function watch (data, key, cb) { // do something}Object.defineProperty
既然要在數據變化后再執行回調,所以需要知道數據是什么時候被修改的,這就是Object.defineProperty的作用,其為數據定義了訪問器屬性。在數據被讀取時會觸發get,在數據被修改時會觸發set。
我們定義一個defineReactive函數,其用來將一個數據變成響應式的:
function defineReactive(data, key) { let val = data[key]; Object.defineProperty(data, key, { configurable: true, enumerable: true, get: function() { return val; }, set: function(newVal) { if (newVal === val) { return; } val = newVal; } });}defineReactive函數為data對象的key屬性定義了get、set,get返回屬性key的值val,set中修改key的值為新值newVal。到目前為止,key屬性還是沒有什么特殊之處。
數據被修改會觸發set,那cb一定是在set中被執行。但set與cb之間好像并沒有什么聯系,所以我們來搭建一座橋梁,來構建兩者的聯系:
let target = null;
我們在全局定義了一個target變量,它用來保存cb的值,然后在set中調用。所以,cb什么時候被保存在target中?回到出發點,我們要調用watch函數來觀測data的key屬性,當值被修改時執行我們定義的回調cb,這就是cb被保存在target中的時機了:
function watch(data, key, cb) { target = cb;}watch函數中target被修改了,但我要是再想調用watch函數一次,也就是說我想在data[key]被修改時,執行兩個不同的回調,又或者說,我想再觀測data的其它屬性,那該怎么辦?必須得在target被再次修改前,將其值保存到別處。因為,target是同個屬性的不同回調或不同屬性的回調所共有的。
我們有必要為key屬性建立一個私有的倉庫,來保存回調。其實defineReactive函數有一點特殊地方:函數內部定義了一個val變量,然后在get和set函數都使用了val變量,這形成一個閉包,defineReactive函數的作用域是key屬性私有的,這就是天然的私有倉庫了:
新聞熱點
疑難解答
圖片精選