JS实现响应式原理

465 阅读4分钟

学习了王老师的课程后,在此对课程做一个记录,并借此捋顺思路方便后期复习,小白不懂,侵权的话联系删除

对象的响应式,是指对象的属性值改变时使用这个属性值的代码会自动执行

  • m有一个初始化的值,有一段代码使用了这个值
  • 那么在m有一个新的值时这段代码会自动重新执行

1、先上代码

清楚实现效果后,我们先来看一段代码

class Depend {
    constructor() {
        this.reactiveFns = []
    }
    // 反应
    addDepend(...fns) {
        this.reactiveFns.push(...fns)
    }
    // 通知
    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}
const dep = new Depend()
console.log(dep);
function watchFn(...fn) {
    dep.addDepend(...fn)
}
watchFn(function(){
    console.log("你好啊,李银河");
    console.log("Hello world");
},function(){
    console.log("锅姨~");
})
dep.notify()

上面的代码完成了依赖的收集,也就是说当有使用属性值的代码的时候就能够被通知到

  • 逻辑是通过Depend类中addDepend方法往reactiveFns数组中添加响应式函数,并通过notify方法执行。
  • 完成以上代码后可以看出,只需要在对象属性值改变时运行depend.notify()方法即可,我们此例中使用Proxy/Reflect代理来进行自动操作

2、管理依赖

然后便就需要考虑依赖的管理问题,我们现在是所有对象属性都共用一个depend,这是不对的,正确情况应该是不通对象的不同属性都应该各自对应一个depend

依赖收集管理.jpg

1644755796(1).jpg 代码如下

class Depend {
    constructor() {
        this.reactiveFns = []
    }
    // 反应
    addDepend(...fns) {
        this.reactiveFns.push(...fns)
    }
    // 通知
    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}
// 响应式的对象
const obj = {
    name: "why",
    age: 18
}
// 封装一个获取depend的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
    // 将对象和key还有依赖depend存起来
    // 获取target对象的key
    let map = targetMap.get(target)
    if(!map) {
        map = new Map()
        targetMap.set(target, map)
    }
    // 通过key获取depend
    let depend = map.get(key)
    if(!depend) {
        depend = new Depend()
        map.set(key, depend)
    }
    return depend
}
// 将对象交给proxy代理
const objProxy = new Proxy(obj, {
    get(target, key, receiver) {
        const depend = getDepend(target, key)
        depend.addDepend(reactiveFn)
        return Reflect.get(target, key, receiver)
    },
    set(target, key, newvalue, receiver) {
        Reflect.set(target, key, newvalue, receiver)
        const depend = getDepend(target, key)
        depend.notify()
    }
})
// 封装一个响应函数
let reactiveFn = null
function watchFn(fn) {
    reactiveFn = fn
        fn()
    reactiveFn = null
}
watchFn(function() {
    console.log("aaaa",objProxy.name);
})
consloe.log("---------------修改值")
objProxy.name = "kobe"
  • 我们首先new一个weakMap,然后再封装一个获取depend的函数,在函数内部用首先声明的target来存对象和属性,再用map来存属性和depend,这样便能做到每一个对象下的每一个属性都拥有一个属于自己的depend,最后再将depen作为函数的返回值返回出去
  • 然后我们需要在对象代理的get方法中收集依赖depend.addDepend(),在set方法中执行依赖depend.notify()
  • 对于响应函数watchFn我们也需要进行重写,首先需要将依赖函数赋值给提前声明的reactiveFn,方便在Proxy代理的get方法中使用,为了正确的收集依赖,所以watchFn函数传入的响应式函数需要执行一次fn()

3、重构Depend类

为了Proxy代理中能够简洁一点,get方法中获取值时只需要调用depend.addDepend()即可,不用判断reactiveFn中是否有值,所以我们需要对Depend类进行重构

let reactiveFn = null
class Depend {
    constructor() {
        this.reactiveFns = new Set()
    }
    /**
     * Depend优化:
     *  1.depend方法
     *  2.使用Set保存依赖函数,而不是数组
     * */ 
    depend() {
        if(reactiveFn) {
            this.reactiveFns.add(reactiveFn)
        }
    }
    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}
  1. 使用set数据结构保存依赖函数,防止重复添加依赖
  2. Dependdepend()方法中执行判断逻辑即可
  3. 为了代码的复用,方便新添加的对象实现响应式,所以我们再封装一个reactive函数
 function reactive(obj) {
    return new Proxy(obj, {
        get(target, key, receiver) {
            // 根据target.key获取对应的depend
            const depend = getDepend(target, key)
            // 给depend添加响应函数
            depend.depend()
    
            return Reflect.get(target, key, receiver)
        },
        set(target, key,newValue, receiver) {
            Reflect.set(target, key,newValue, receiver)
            const depend = getDepend(target, key)
            depend.notify()
         }
    })
 }
 // 对象的响应式
const obj = reactive({
    name: 'why',
    age: 18
})

到这响应式原理便基本完成,这里使用的数据代理实现的响应式,在Vue2中使用的是Object.defineProperty(),进行数据劫持完成响应式

最后总结一下:

  • 首先我们需要一个Depnd类来收集依赖,封装了一个watchFn函数来传入依赖函数
  • 然后通过函数getDepend对收集的依赖进行管理,在数据代理中添加依赖depend.depend()和执行depend.notify()
  • 最后为了方便新增函数使用响应式,所以再封装一个reactive函数包裹数据代理的代码