玩轉Vuejs--數組監聽
- 數組本身的賦值;
- 數組push等方法的使用導致的變化;
- 數組中的值變化導致的變化;
- 操縱數組長度導致的數組變化;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script src="./../../dist/vue.js"></script> </head> <body> <div></div> <div id="demo"> <div> {{testArry}} </div> <input type="button" value="按鈕" @click=‘clickHandler‘/> </div> </body> <script> new Vue({ el:"#demo", data: { testArry: [1, 2, 3] }, methods:{ clickHandler(){ this.testArry = [4, 5, 6]//直接賦值
//this.testArry[0] = 5; //this.testArry = this.testArry;//改變數組中的值//this.testArry.push(6);//調用方法
//this.testArry.length = 1;//改變長度
} } }); </script> </html>
testArry是data(key、value形式)的一個屬性,在初始化的時候 new Observer的時候會調用defineReactive進行正常監聽,數據更新時通知訂閱的watchers進行更新;
2.數組push等操作改變數據時想要監聽到數據的變化是沒辦法繼續通過defineProperty來實現的,需要直接監聽push等方法,在調用方法時進行監聽,所以考慮對數組原型上的方法進行hook,之後再將hook後的方法掛在到所要監聽的數組數據的 __proto__上即可,過程如下:
首先hook數組原型方法(如push)
var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto); var methodsToPatch = [ ‘push‘, ‘pop‘, ‘shift‘, ‘unshift‘, ‘splice‘, ‘sort‘, ‘reverse‘ ]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method var original = arrayProto[method]; def(arrayMethods, method, function mutator () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; var result = original.apply(this, args); var ob = this.__ob__; var inserted; switch (method) { case ‘push‘: case ‘unshift‘: inserted = args; break case ‘splice‘: inserted = args.slice(2); break } if (inserted) { ob.observeArray(inserted); } // notify change ob.dep.notify();//通知watchers return result }); });
這裏沒有hook沒有直接在Array.prototype上做,而是重新創建了一份原型對象 arrymethods 出來,既保留了方法的整個原型鏈,又避免了汙染全局數組原型。
之後在觀察數據時進行掛載:瀏覽器支持 __proto__ 那麽直接掛載到數組的 __proto__上;不支持重新定義一份到數組本身;
var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; def(value, ‘__ob__‘, this); if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); //支持__proto__:此處直接進行掛載 } else { copyAugment(value, arrayMethods, arrayKeys); //不支持__proto__:直接定義到數組上 } this.observeArray(value); } else { this.walk(value); } }; /** * Augment a target Object or Array by intercepting * the prototype chain using __proto__ */ function protoAugment (target, src) { /* eslint-disable no-proto */ target.__proto__ = src; /* eslint-enable no-proto */ } /** * Augment a target Object or Array by defining * hidden properties. */ /* istanbul ignore next */ function copyAugment (target, src, keys) { for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; def(target, key, src[key]); } }
這裏需要註意數據更新方面,Vue的數據更新都是通過依賴收集器(Dep實例)通知觀察者(Watcher實例)來進行的。數組這裏與對象的初始化不同,從性能上考慮掛載的方法在最開始就會且僅會初始化一次,那麽就會導致dep實例的初始化與更新不在同一個作用域下。Vue的處理是給數組一個 __ob__的屬性用來掛載數據,在push等操作觸發hook 時再從數據的__ob__屬性上取出 dep進行通知,這裏處理的就很靈性了。
3.數組中的值變化,如果是對象或數組顯然會遞歸處理直到基本類型,Vue對基本類型的數據是不進行觀察的,主要也無法建立起監聽,所以數組下標直接改變數組值這種操作不會觸發更新;
4.數組長度的變化,無法監聽,所以數組長度變化也不會觸發更新;
三、總結:
Vue對數據的監聽有兩種,一種是數組本身的變化,直接通過Object.defineProperty實現;另一種是通過方法操縱數組,此時會hook原型上的方法建立監聽機制;對於數組下標以及長度的變化沒有辦法直接建立監聽,此時可以通過$set進行更新(會調用hook中的splice方法觸發更新);對於數組長度變化導致的數據變化無法監聽,如果想觸發只能通過hack方式調用hook中的方法進行更新;
玩轉Vuejs--數組監聽