Vue2 响应式原理:DefinePropertyの秃头危机

52 阅读3分钟

Object.defineProperty()

用于数据劫持和数据代理,能够检测对象属性的变化

Object.defineProperty(obj,'key',configOption) //新增或者修改属性
configOption = {
value,
enumerable,
writable,
get,
set,
...
}

使用defineObject定义的getter setter需要使用外部变量周转才能使用

  • setter 设置 temp
  • getter 返回 temp

defineReactive

将temp封装到内部,通过闭包的方式在defineReactive保存了Object.defineProperty的temp临时变量

functino defineReactive(data,key,val) {
    Object.defineProperty(data,key,{
        enumberabel:true,
        configurabel:true,
        get(){
            return val
        },
        set(newValue){
            val = newValue
        }
    }
}

Observer

通过递归生成深层次响应式内容

image.png

数组的响应式

Observer

image.png

数组不用去执行 defineProperty 方法,他的循环从 Observer实例 到 observe 在 Observer 实例中改变原型,调用observer,在新原型方法上再次触发 observe 方法,将新增的内容变为响应式内容

改写7个数组方法

  1. push
  2. pop
  3. shift
  4. unshift
  5. splice
  6. sort
  7. reverse

改写 Array.prototype 上的7个操作方法

其中push , unshift , splice 这些需要推入新数据的需要保证新数据也是 observed 的,从 ob 上获取observer实例,调用其 ArrayWalk 遍历方法

image.png

以Array.prototype为原型创建了一个arrayMethods,

将数组的原型指向 arrayMethods,调用改写后的7个方法

  1. ArrayMethods = Obejct.create(Array.prototype)

  2. 改写ArrayMethods上7个方法

  3. 修改数组原型

    1. o.proto = arrayMethods 或者
    2. Obejct.setPrototypeOf(o,arrayMethods)
methodNeedChange.forEach(methodName => {
    const original = arrayPrototype[methodName]
    def(arrayMethods,methodName,function() {
        const args = [...arguments]
        // 将新增的内容也变成响应式的
        switch (methodName):
            {
            case 'push':
            case 'unshift':
                inserted = args 
            case 'splice':
                inserted = args .slice(2)
                break;
            }
        if(inserted){
            this.__ob__.observerArray(inserted)
        }
        return  original.apply(this,args ) 
        // 不能使用箭头函数,因为我们需要this和arguments
    } 
})

原因

数组也可以视作对象,使用Object.defineProperty也能监听数组元素的修改,但无法监听数组方法操作的,所以vue干脆直接放弃了使用Object.defineProperty方法

依赖收集

  • 在getter中收集依赖,收集的是watcher
  • 在setter中触发依赖,触发的也是watcher
  • 每一个Observer实例中有一个Deps实例

Watcher 可以理解为 vue3 中的 effect

Dep Class

拥有闭包全局唯一id

在哪里实例化

  1. Dep 类在 Observer 的实例中被实例化 ,每一个对象都会有一个Observer实例,所以就有一个Dep实例。用于保存当前响应式对象的Watcher(effect 依赖)

  2. 在defineReactive 的时候实例化Dep,这个是最后一层非对象的响应式数据的Dep, 是在defineReactive的闭包中的

Dep.depend 添加收集

通过全局变量 Dep.target (全局的)判断当前触发getter的函数,从Vue3的角度看回来,这个就是activeEffect的意思

将Dep.target(watcher实例)添加到Dep中

  • 由响应式数据的getter触发

  • defineReactive中定义,调用Dep的depend方法

  • 判断是否有Watcher,将Watcher 添加到Dep的订阅者列表中

Dep.notify 触发依赖

Dep中获取到Watcher的实例列表

  • 由数据的setter触发

  • defineReactive中定义调用 Dep 的 notify 方法

  • 通知所有watcher,执行回调函数,如果是render watcher 则触发更新流程

Watch Class

闭包全局唯一id

依赖收集

组件渲染或者计算属性的时候会创建Watcher实例(Effect),在渲染的时候将本Watcher实例设置Dep.target(全局),从而触发依赖手机

更新操作

Watcher 的update函数相当于Vue3的effect,当响应式内容变化时,依赖被触发,watcher的update被执行,重新计算或者渲染视图

笔者才疏学浅,各位读者多多担待,不吝赐教。部分插图来自网络,侵删。