手写Vue3.0响应式原理

// Vue3.0 响应式原理

 const toProxy = new WeakMap() // 放置的是 原对象:代理过的对象  防止多次代理同一个对象
 const toRaw = new WeakMap() // 放置的是 代理过的对象:原对象 防止代理已经代理过的对象


 // 判断是否为对象
 function isObject(val) {
    return typeof val === 'object' && val !== null
 }

 // 判断key是否在这个对象上面
 function hasOwn(target, key) {
     return Reflect.has(target, key)
 }

// 响应式的核心方法
function reactive(target) {

    // 创建响应式对象
    return createReactiveObject(target)

}

// 创建响应式对象的方法 代理对象
function createReactiveObject(target) {

    if (!isObject(target)) { // 如果不是对象,直接返回
        return target
    }

    const alreadyProxy = toProxy.get(target) // 如果已经代理过了 就将代理过的对象返回
    if(alreadyProxy) {
        return alreadyProxy
    }

    if (toRaw.has(target)) { // 防止被代理过的对象被多次代理
        return target
    }

    const baseHandler = {
        get(target, key, receiver) {
            const result = Reflect.get(target, key, receiver)

            // 收集依赖 订阅 把当前的 key 和 effect 对应起来
            track(target, key) // 如果目标上的 key 变化了 重新让数组中的 effect 执行即可
            
            return isObject(result) ? reactive(result) : result // 如果是对象的话就再代理一次,否则返回当前数据
        },

        set(target, key, value, receiver) {
            // 修改数组的时候,先修改值,然后length+1,会出发两次set
            const hadKey = hasOwn(target, key) // 判断对象时候有这个属性
            const oldValue = target[key]
            const res = Reflect.set(target, key, value, receiver)
            if (!hadKey) { // 如果对象上没有这个属性,就证明是新增一个属性
                console.log('新增属性')
            } else if (oldValue !== res) { // 如果老值和新值不相等,证明修改是有意义的,为了屏蔽无意义的修改
                console.log('修改属性')
            }

            const result = Reflect.set(target, key, value, receiver)
            return result
        },

        deleteProperty(target, key) {
            let result = Reflect.deleteProperty(target, key)
            return result
        }
    }

    const observed = new Proxy(target, baseHandler)

    toProxy.set(target, observed)
    toRaw.set(observed, target)
    return observed
}

// const obj = {name: 'VUE', version: {name: 'vue2'}}
// let proxy = reactive(obj)
// let proxy1 = reactive(obj)
// let proxy2 = reactive(proxy)

// proxy.name
// proxy.name = 'VUE3.0'
// proxy.version.name = 'vue3'
// console.log(proxy.name)
// console.log(proxy.version.name)

Vue3.0的需要注意的点:
1.我们都知道Vue2.0代理对象会默认递归,所以在数据层级特别深的情况下,响应的速度会慢,Vue3.0的代理是在get()里面对当前属性进行代理,用就代理,不用就不代理,而且还会通过toProxy这个weakMap将代理过的对象存储起来,防止对象被多次代理,这样我们就判断是否代理过当前对象,如果没有代理过,那就对新对象代理;反之,如果代理过了这个对象,那我们就直接返回代理过的对象,就行了。还有一种情况是,代理过的对象又走了reactive()方法,这样就会导致对代理过的对象返回的Proxy再进行代理,显然我们是不希望看见这样的现象的,所以我们将已经代理过的对象存到toRaw这个weakMap里面,然后我们判断是否代理过这个对象,如果代理过,同样就返回代理过的对象就行了,这里一定要注意和toProxy区分开来。显然我们能发现Vue3.0的这种实现在性能上远剩于Vue2.0。
2.Vue3.0是能对数组进行代理的,它能够监听到数组的变化。但是我们知道当一个数组发生变化时,实际上过程是这样的:如果我们给数组push一个数据,它会先在数据的末尾添加一个数据,然后改变数组的length加1,这样出现的问题就是,会出发两次set()方法,所以我们需要将新值和老值进行比较,如果不相同的话,那么就证明这个值是有意义的,它不是length这种属性,我们就执行赋值操作就可以了。
3.由于ES5的限制,ES6新增的Proxy无法被转译成ES5,所以Proxy的兼容性就有了问题,也就导致Vue3.0无法兼容ie,所以未来想要上Vue3.0的人需要注意着一点,当然了,尤雨溪说未来Vue3.0会兼容到ie11,现在Vue3.0还处于release版本,估计正式版也在不就后就会推出,就可以考虑学习一下了。
4.我比较推荐看源码的时候看beta版本,packages包比较少,而且目录比较清晰,学习起来更加容易一些。

如果有写的不足的地方,欢迎在下方交流讨论

posted @ 2020-08-12 22:37  毛小星  阅读(383)  评论(0编辑  收藏  举报