Vue中对数据的监听主要依靠Object.defineProperty
来实现的,这种实现主要针对key/value
形式的对象,对数组中的值的变化是无能为力的,definrProperty
是无法监听数组长度的变化,监听索引的代价也很高,那么应该怎么对数组中的数据进行监听呢?
push
等方法导致的变化这种情况和对象的监听是一致的,直接使用defineProperty
对数据进行监听就可以了。
数组push
等操作改变数据时想要监听数据的变化就没有办法通过defineProperty
来实现了。那要怎么实现?
Object.create
实现一个Array.prototype
的继承者arraymethods
。它访问的方法和Array.prototype上的是一样的。我们不能直接在Array.prototype上对方法进行监听,因为这样会影响到正常方法的调用。defineproperty
直接写入到arraymethods
对象上:const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'reverse',
'sort'
];
const arrayProto = Array.prototype, //缓存Array的原型
arrayMethods = Object.create(arrayProto); //继承Array的原型
//设置对象属性的工具方法
function def(obj, key, val) {
Object.defineProperty(obj, key, {
value: val,
enumerable: true,
writable: true,
configurable: true
});
}
methodsToPatch.forEach(function(method, index) {
def(arrayMethods, method, function(...args) {
//arrayProto[method].apply(this, args);代表原来的方法
});
});
arraymethods
上的七种方法,会直接访问到arraymethods
对象上的,可以在里面写一些需要监听的方法,内部通过arrayProto[method].apply(this,args)
访问到数组上的真正的方法。arraymethods
是我们构造出来的结构,它本身并不是数组,所以我们要对我们操作的数组通过以下方法让其指向arraymethods
中的方法,而不是真正的Array.prototype
中的方法。if('__proto__' in {}) {
//浏览器中有__proto__,将数组的原型指向arrayMethods,这样当数组调用上述的7个方法时,其实是调用arrayMethods中的方法而不是调用Array.prototype中的方法
target.__proto__ = arrayMethods;
} else {
//如果浏览器不支持__proto__,那么直接将arraymethods中的方法拷贝到数组实例中。则设置数组对应的属性,这样当数组调用上述的7个方法时,其实是调用数组对应属性指向的方法
for(let i = 0, l = methodsToPatch.length; i < l; i++) {
let key = methodsToPatch[i];
def(target, key, arrayMethods[key]);
}
}
完整代码:
const patchArray = (function() {
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'reverse',
'sort'
];
//设置对象属性的工具方法
function def(obj, key, val) {
Object.defineProperty(obj, key, {
value: val,
enumerable: true,
writable: true,
configurable: true
});
}
const arrayProto = Array.prototype, //缓存Array的原型
arrayMethods = Object.create(arrayProto); //继承Array的原型
methodsToPatch.forEach(function(method, index) {
def(arrayMethods, method, function(...args) {
//首先调用Array原型的方法
const old=this.concat([]);
const res = arrayProto[method].apply(this, args);
let inserted = null,
deleted = null;
let _callback_ = this._callback_;
//记录插入的值
switch(method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
//这是新增的
inserted = args.slice(2);
let start = args[0],
end = start + args[1];
deleted = old.slice(start, end);
break;
case 'pop':
case 'shift':
deleted = res;
}
_callback_(inserted, deleted);
return res;
});
});
return function(target, callback) {
def(target, '_callback_', callback); //定义回调
//看看浏览器支不支持__proto__这个属性,通过改变__proto__的值,可以设置对象的原型
if('__proto__' in {}) {
//将数组的原型指向arrayMethods,这样当数组调用上述的7个方法时,其实是调用arrayMethods中的方法而不是调用Array.prototype中的方法
target.__proto__ = arrayMethods;
} else {
//如果浏览器不支持__proto__,则设置数组对应的属性,这样当数组调用上述的7个方法时,其实是调用数组对应属性指向的方法
for(let i = 0, l = methodsToPatch.length; i < l; i++) {
let key = methodsToPatch[i];
def(target, key, arrayMethods[key]);
}
}
}
})();
//测试
let arr = [1, 2, 3];
patchArray(arr, function(add, del) {
if(add)
console.log('这是新增的内容:', add);
if(del)
console.log('这是删除的内容:', del);
});
arr.splice(1,2,'aa','bb','cc')
参考文档:http://www.qiutianaimeili.com/html/page/2019/05/m0kcbzlpc9s.html
https://www.cnblogs.com/DevinnZ/p/10569033.html