<!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進行更新;html
2.數組push等操做改變數據時想要監聽到數據的變化是沒辦法繼續經過defineProperty來實現的,須要直接監聽push等方法,在調用方法時進行監聽,因此考慮對數組原型上的方法進行hook,以後再將hook後的方法掛在到所要監聽的數組數據的 __proto__上便可,過程以下:vue
首先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__上;不支持從新定義一份到數組自己;app
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對基本類型的數據是不進行觀察的,主要也沒法創建起監聽,因此數組下標直接改變數組值這種操做不會觸發更新;this
4.數組長度的變化,沒法監聽,因此數組長度變化也不會觸發更新;spa
3、總結:prototype
Vue對數據的監聽有兩種,一種是數組自己的變化,直接經過Object.defineProperty實現;另外一種是經過方法操縱數組,此時會hook原型上的方法創建監聽機制;對於數組下標以及長度的變化沒有辦法直接創建監聽,此時能夠經過$set進行更新(會調用hook中的splice方法觸發更新);對於數組長度變化致使的數據變化沒法監聽,若是想觸發只能經過hack方式調用hook中的方法進行更新,如官方推薦的splice等;eslint