什么是數據響應式
從一開始使用 Vue 時,對于之前的 jq 開發而言,一個很大的區別就是基本不用手動操作 dom,data 中聲明的數據狀態改變后會自動重新渲染相關的 dom。
換句話說就是 Vue 自己知道哪個數據狀態發生了變化及哪里有用到這個數據需要隨之修改。
因此實現數據響應式有兩個重點問題:
如何知道數據發生了變化? 如何知道數據變化后哪里需要修改?對于第一個問題,如何知道數據發生了變化,Vue3 之前使用了 ES5 的一個 API Object.defineProperty Vue3 中使用了 ES6 的 Proxy,都是對需要偵測的數據進行 變化偵測 ,添加 getter 和 setter ,這樣就可以知道數據何時被讀取和修改。
第二個問題,如何知道數據變化后哪里需要修改,Vue 對于每個數據都收集了與之相關的 依賴 ,這里的依賴其實就是一個對象,保存有該數據的舊值及數據變化后需要執行的函數。每個響應式的數據變化時會遍歷通知其對應的每個依賴,依賴收到通知后會判斷一下新舊值有沒有發生變化,如果變化則執行回調函數響應數據變化(比如修改 dom)。
下面詳細分別介紹 Vue2 及 Vue3 的數據變化偵測及依賴收集。
Vue2
變化偵測
Object 的變化偵測
轉化響應式數據需要將 Vue 實例上 data 屬性中定義的數據通過遞歸將所有屬性都轉化為 getter/setter 的形式,Vue 中定義了一個 Observer 類來做這個事情。
function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true })}class Observer { constructor(value) { this.value = value; def(value, '__ob__', this); if (!Array.isArray(value)) { this.walk(value); } } walk(obj) { for (const [key, value] of Object.entries(obj)) { defineReactive(obj, key, value); } }}
直接將一個對象傳入 new Observer() 后就對每項屬性都調用 defineReactive 函數添加變化偵測,下面定義這個函數:
function defineReactive(data, key, val) { let childOb = observe(val); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { // 讀取 data[key] 時觸發 console.log('getter', val); return val; }, set: function (newVal) { // 修改 data[key] 時觸發 console.log('setter', newVal); if (val === newVal) { return; } val = newVal; } })}function observe(value, asRootData) { if (typeof val !== 'object') { return; } let ob; if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__; } else { ob = new Observer(val); } return ob;}
函數中判斷如果是對象則遞歸調用 Observer 來實現所有屬性的變化偵測,根據 __ob__ 屬性判斷是否已處理過,防止多次重復處理,Observer 處理過后會給數據添加這個屬性,下面寫一個對象試一下:
const people = { name: 'c', age: 12, parents: { dad: 'a', mom: 'b' }, mates: ['d', 'e']};new Observer(people);people.name; // getter cpeople.age++; // getter 12 setter 13people.parents.dad; // getter {} getter a
打印 people 可以看到所有屬性添加了 getter/setter 方法,讀取 name 屬性時打印了 people.age++ 修改 age 時打印了 getter 12 setter 13 說明 people 的屬性已經被全部成功代理監聽。
新聞熱點
疑難解答