vue数据变化监测----array数据的监测

本文深入探讨Vue的MVVM实现,重点在于分析Array数据变化监测的难题。由于Array无法直接通过defineReactive收集依赖,所以在push、splice等操作后无法自动更新视图。解决方案是通过拦截数组的变异方法,手动触发依赖更新,从而实现响应式效果。

先要看模拟Vue的watcher,observer,dep实现简单的MVVM

1. 首先看Observe类的代码,可以知道,array是无法进入defineReactive里面去收集依赖的

2. 无法收集依赖,意味着当array执行push,splice,pop,shift等操作时,数组因为没有依赖,所以无法实现更新

3. 此时,能不能让数组在不进入defineReactive函数的前提下,强行去收集依赖呢?

function observe (data) {
    if (!data || typeof data !== 'object') {
        return
    }
    return new Observe(data)
}
// 定义属性
function def(target, key, value, enumerable) {
    Object.defineProperty(target, key, {
        enumerable: !!enumerable,
        configurable: true,
        writable: true,
        value
    })
}
// 针对数组,让其递归收集依赖
function dependArray (value) {
    for (let e, i = 0, l = value.length; i < l; i++) {
        e = value[i]
        /**
         * e对象数组里面的某一项数据
         * 对于非引用型数据,不需要关心dep
         * 对于引用型数据,若想要取到dep
         * 此时就要利用Observer的构造函数内的def(data, '__ob__', this)方法
         * 这个方法为数据创建了一个__ob__属性,属性指向Observer实例
         * 则这个数据就可以用__ob__.dep拿到dep,然后去depend收集依赖
        */
        e && e.__ob__ && e.__ob__.dep.depend()
        if (Array.isArray(e)) {
            dependArray(e)
        }
    }
}
class Observer {
    constructor (data) {
        def(data, '__ob__', this) 
        // 为data添加__ob__属性,属性值为当前observer,这时给array数据专门提供的访问dep的途径
        this.dep = new Dep()
        /** 
         * data所对应的dep,eg: data: {person: {name: 'rose'}},
         * 那么,这个dep就可以相当于这个键值对的键(data)所对应的dep
         * 由于array数据无法进入defineReactive函数,故要在这儿申明一个dep,
         * 这个dep就是为了数组的更新而使用
         * **/
        if (Array.isArray(data)) {
            this.observeArray(data)
        } else {
            this.walk(data)
        }
    }
    observeArray(array) {
        for (let i = 0, len = array.length; i < len; i++) {
            observe(array[i])
        }
    }
    walk(object) {
        for (let i in object) {
            this.defineReactive(object, i, object[i])
        }
    }
    defineReactive(target, key, value) {
        const dep = new Dep()
        /**
         * 此时这个dep
         * 相当于就是data: {person: {name: 'rose'}}
         * 这个键值对的值{person: {name: 'rose'}}
         * 所对应的dep
         * 其与上面那个dep可以看作成父子级关系
         * */
        const childOb = observe(value)
        /**
         * 此时childOb.dep指向的就是person: {name: 'rose'}这个键值对的键person所对应的dep
         * 以此推到,可以看作是形成了有层级关系的dep
        */
        Object.defineProperty(target, key, {
            configurable: true,
            get: function() {
                dep.depend() // 收集依赖
                if (childOb) { 
                    childOb.dep.depend()
                    /**
                     * 子级dep也要收集依赖,这个就是为array准备的
                     * 最开始分析,array数据的dep本身是不会收集依赖的
                     * 但是可以让他强行进行一次收集,就是在这儿
                     * */
                    if (Array.isArray[value]) {
                        dependArray(value)
                    }
                    /**
                     * 如果value是一个数组
                     * 则上面有一步:const childOb = observe(value)已经通过递归,为数组以及数组里面的引用类型的数据new好了observe对象,每一个observe,都会拥有一个dep
                     * 这时,通过dependArray函数,
                     * 通知array子级的dep,去强行收集这次的依赖,dep.depend()
                    */
                }
                return value
            }
        })
    }
}

4. 到这里依赖可以收集了,但是如何让数组在做了push等操作后,去通知这些依赖执行更新呢?

5. 此时需要做的就是,拦截push等操作

const arrayProto = Array.prototype // 获取数组的原型上的方法
const arrayMethodObj = Object.create(arrayProto) // 创建一个对象,将其原型指向arrayProto
const operateAry = ['push', 'pop', 'shift', 'unshift', 'splice'] // 需要拦截的数组操作
for (let i = 0, len = operateAry.length; i < len; i++) {
    const method = operateAry[i]
    const origin = arrayProto[method]
    Object.defineProperty(arrayMethodObj, method, {
        enumerable: true,
        configurable: true,
        writable: true,
        value: function(...args) {
            const result = origin.apply(this, args)
            const ob = this.__ob__ // 这儿的用法同dependArray(value),就是为了取得dep
            let inserted
            switch(method) {
                case 'push':
                case 'unshift':
                    inserted = args
                    break
                case 'splice':
                    inserted = args.slice(2)
                    break
            }
            if (inserted) { // 如果有新的数据插入,则插入的数据也要进行一个响应式
                ob.observeArray(inserted)
            }
            // 通知依赖进行更新
            ob.dep.notify()
            return result
        }
    })
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值