vue 如何监听Array的变化

Object.defineProperty对数组进行响应式化是有缺陷的,虽然我们可以监听到索引的改变。 

function def (obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function() {
            console.log('获取');
            return val;
        },
        set: function(newVal) {
            if(val === newVal) {
                return;
            }
            val = newVal;
            console.log("数据改变了");
        }
    });
}

var data = [1, 2, 3];
def(data, 0, 2);

 我们可以查看data结构如下:

vue 如何监听Array的变化_第1张图片

console.log(data[0])
// 获取
// 2

data[0] = 100
// 数据改变了


data.length = 0; // 控制台无任何输出

但是defineProperty不能检测到数组长度的变化,准确来说是通过改变length而增加的长度不能检测到。

Vue监听Array三部曲:

第一步:先获取原生Array的原型方法,因为拦截后还是需要原生的方法帮我们实现数组的变化;

第二步:对Array的原型方法使用Object.defineProperty做一些拦截操作;

第三步:把需要被拦截的Array类型的数据原型指向改造后原型。 

 在进行数据observer绑定的时候,我们先判断是否

// 判断是否有__proto__,因为部分浏览器是没有__proto__属性
var hasProto = '__proto__' in {};
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
var methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];

function def (obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
        value: val,
        enumerable: !!enumerable,
        configurable: true,
        writable: true
    });
}
methods.forEach(function(method) {
    var original = arrayMethods[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) {
            
        }
        return result;
    });
})

var arrayKeys = Object.getOwnPropertyNames(arrayMethods);

var Observer = function Observer (value) {
    this.value = value;
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
        // 如果有__proto__,直接覆盖
        if (hasProto) {
            protoAugment(value, arrayMethods);
        } else {
            // 没有就把方法加到属性自身上
            copyAugment(value, arrayMethods, arrayKeys);
        }
    }
}

// 原型的赋值
function protoAugment (target, src) {
    target.__proto__ = src;
}

// 赋值
function copyAugment (target, src, keys) {
    for (var i = 0, l = keys.length; i < l; i++) {
        var key = keys[i];
        def(target, key, src[key]);
      }
}

参考资料: https://www.jb51.net/article/162584.htm

你可能感兴趣的:(vue)