5 Vue中如何检测数组变化

前言

Vue 不能检测到以下数组的变动:

  • 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  • 当你修改数组的长度时,例如:vm.items.length = newLength

Vue 提供了以下操作方法

    // Vue.set
    Vue.set(vm.items, indexOfItem, newValue)
    // vm.$set,Vue.set的一个别名
    vm.$set(vm.items, indexOfItem, newValue)
    // Array.prototype.splice
    vm.items.splice(indexOfItem, 1, newValue)

分析

数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择对 7 种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写(AOP 切片思想)

所以在 Vue 中修改数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新

  • 用函数劫持的方式,重写了数组方法,具体呢就是更改了数组的原型,更改成自己的,用户调数组的一些方法的时候,走的就是自己的方法,然后通知视图去更新
  • 数组里每一项可能是对象,那么我就是会对数组的每一项进行观测(且只有数组里的对象才能进行观测,观测过的也不会进行观测)

原理

Vuedata 中的数组,进行了原型链重写。指向了自己定义的数组原型方法,这样当调用数组api 时,可以通知依赖更新,如果数组中包含着引用类型。会对数组中的引用类型再次进行监控。

手写简版分析

    let oldArray = Object.create(Array.prototype);
    ['shift', 'unshift', 'push', 'pop', 'reverse','sort'].forEach(method => {
        oldArray[method] = function() { // 这里可以触发页面更新逻辑
            console.log('method', method)
            Array.prototype[method].call(this,...arguments);
        }
    });
    let arr = [1,2,3];
    arr.__proto__ = oldArray;
    arr.unshift(4);

源码分析

    // 拿到数组原型拷贝一份
    const arrayProto = Array.prototype 
    // 然后将arrayMethods继承自数组原型
    // 这里是面向切片编程思想(AOP)--不破坏封装的前提下,动态的扩展功能
    export const arrayMethods = Object.create(arrayProto) 
    const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]
    
    methodsToPatch.forEach(function (method) { // 重写原型方法 
        const original = arrayProto[method] // 调用原数组的方法 
    
        def(arrayMethods, method, function mutator (...args) { 
            // 这里保留原型方法的执行结果
            const result = original.apply(this, args) 
            // 这句话是关键
            // this代表的就是数据本身 比如数据是{a:[1,2,3]} 那么我们使用a.push(4)  this就是a  ob就是a.__ob__ 这个属性就是上段代码增加的 代表的是该数据已经被响应式观察过了指向Observer实例
            const ob = this.__ob__ 
    
            // 这里的标志就是代表数组有新增操作
            let inserted
            switch (method) { 
                case 'push': 
                case 'unshift': 
                    inserted = args 
                    break 
                case 'splice': 
                    inserted = args.slice(2) 
                    break 
            }
            // 如果有新增的元素 inserted是一个数组 调用Observer实例的observeArray对数组每一项进行观测
            if (inserted) ob.observeArray(inserted) 
    
            ob.dep.notify() // 当调用数组方法后,手动通知视图更新 
    
            return result 
        }) 
    })
    
    this.observeArray(value) // 进行深度监控

vue3:改用 proxy ,可直接监听对象数组的变化

Last Updated:
Contributors: leeguooooo