Vue資料雙向繫結原理 ( Vue.version = 2.6.14 )
阿新 • • 發佈:2022-03-07
Vue資料雙向繫結原理;Vue如何監測Object和Array的變化,其中的不足以及Vue的解決方案
中
Vue資料雙向繫結原理 ( Vue.version = 2.6.14 )
分析主要程式碼
1. 監測Object的變化
在vue.commone.dev.js
檔案中,Vue在defineReactive$$1
方法中監聽物件的屬性變化。
/** * defineReactive$$1 */ function defineReactive$$1 ( obj, key, val,監測 customSetter, shallow ) { // 建立Dep例項 const dep = new Dep(); // 獲取屬性預設的 getter/setter ,防止重新定義getter/setter 的時候覆蓋預設行為 const descriptor = Object.getOwnPropertyDescriptor(obj, key); const getter = descriptor && descriptor.get; const setter = descriptor && descriptor.set; if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function() { const value = getter ? getter.call(obj) : val; if (Dep.target) { /** * 1. 呼叫Dep.prototype.depend * 2. 在Dep.prototype.depend中呼叫Watcher.prototype.addDep * 3. Watcher.prototype.addDep中將Dep例項儲存到Watcher例項的newDepIds、newDeps屬性上,再將Watcher例項儲存到Dep例項的subs屬性上 */ dep.depend(); } return value; }, set: function(newVal) { const value = getter ? getter.call(obj) : val; // 設定新的值 if (setter) { setter.call(obj, newVal); } else { val = newVal; } /** * 1. 呼叫Dep.prototype.notify * 2. 在Dep.prototype.notify中遍歷Dep例項上的subs屬性 ( getter中儲存的Watcher例項 ) * 3. 呼叫Watcher.prototype.update更新檢視 ( 具體update中做了什麼,下次單獨分析 ) */ dep.notify(); } }) }
整體流程:
① 通過observer
,使用Object.defineProperty
讓data
的獲取、變化能夠被監測
② 獲取data
時,觸發getter
, 將依賴
新增到watcher
中,將wacher
新增到依賴
的subs ( 訂閱? )
中
③ 更新data
時,觸發setter
,通知依賴
遍歷subs
,並呼叫watcher
的update
方法更新檢視 ( 其實最終呼叫的是Vue例項
的_render
方法 )
疑問:
① watcher
是在何時建立的?
在initComputed
階段建立watcher
,並且watcher的vm屬性
指向Vue例項
,將watcher
儲存到Vue例項的_watchers
不足:
- 向物件中新增或刪除屬性時,無法監測到變化
- 無法監測陣列的變化
2. 監測Array的變化
定義攔截器
,在呼叫修改
陣列的方法時,觸發攔截器。
const arrayProto = Array.prototype; const arrayMethods = Object.create(arrayProto); // 能夠改變陣列自身的7個方法 const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]; // 遍歷methodsToPatch並修改arrayMethods上對應名稱的方法 methodsToPatch.forEach(method => { Object.defineProperty( arrayMethods, method, { value: function() { /** * clone引數 */ const args = []; let len = arguments.length; while (len--) { args[len] = arguments[len]; } // 呼叫原生的方法 const result = arrayProto[method].apply(this, args); /** * 呼叫 push/unshift/splice時若新增/更新 了新元素 * 則呼叫observe觀察該元素 */ const ob = this.__ob__; let inserted; switch (method) { case 'push': case 'unshift': inserted = args; break; case 'splice': inserted = args.slice(2); break; default: break; } if (inserted) { ob.observeArray(inserted); } // 通知依賴更新 ob.dep.notify(); return result; } } ) })
整體流程:
① 給Array.prototype
上能改變陣列本身的方法
新增攔截器
② 在Observer的構造器中
將Array例項的__proto__
指向arrayMethods
③ 呼叫這些方法時,通知依賴更新
不足:
- 通過
索引
修改陣列時,無法監測到陣列的變化
3. Vue對以上不足的解決方案
增加了兩個全域性API:Vue.set
和Vue.delete
。