- 数组本身的赋值;
- 数组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中的方法进行更新,如官方推荐的splice等;
转载于:https://www.cnblogs.com/DevinnZ/p/10569033.html