vue的数组监听

Vue中对数据的监听主要依靠Object.defineProperty来实现的,这种实现主要针对key/value形式的对象,对数组中的值的变化是无能为力的,definrProperty是无法监听数组长度的变化,监听索引的代价也很高,那么应该怎么对数组中的数据进行监听呢?

一、数组的变化情况:

  1. 数组本身的赋值
  2. 数组中push等方法导致的变化
  3. 数组中的值变化
  4. 操作数组的长度导致的变化

二、对上面的变化依次分析:

数组本身的赋值

这种情况和对象的监听是一致的,直接使用defineProperty对数据进行监听就可以了。

数组中push等方法导致的变化

数组push等操作改变数据时想要监听数据的变化就没有办法通过defineProperty来实现了。那要怎么实现?

  1. 需要通过Object.create实现一个Array.prototype继承者arraymethods。它访问的方法和Array.prototype上的是一样的。我们不能直接在Array.prototype上对方法进行监听,因为这样会影响到正常方法的调用。
  2. 将push等方法,通过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);代表原来的方法
    });
});
  1. 此时,如果直接访问arraymethods上的七种方法,会直接访问到arraymethods对象上的,可以在里面写一些需要监听的方法,内部通过arrayProto[method].apply(this,args)访问到数组上的真正的方法。
  2. 因为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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章