由浅入深,带你用JavaScript实现响应式原理( 六 )


由浅入深,带你用JavaScript实现响应式原理

文章插图
 
5.7.对象的依赖收集优化
可以发现上面打印的结果中的响应式函数数组全部为空 , 是因为在前面收集响应式函数是通过watchFn来收集的 , 而在getDepend中并没有去收集对应的响应式函数 , 所以返回的dep对象里面的数组全部就为空了 。如果对响应式函数 , 还需要通过自己一个个去收集 , 是不太容易的 , 所以可以监听响应式函数中依赖了哪一个对象属性 , 让Proxy的get捕获器去收集就行了 。
  • 既然get需要监听到响应式函数访问了哪些属性 , 那么响应式函数在被添加之前肯定是要执行一次的;
  • 如何在Proxy中拿到当前需要被收集的响应式函数呢?可以借助全局变量;
  • 下面就来对watchFn进行改造;
// 定义一个全局变量 , 存放当前需要收集的响应式函数let currentReactiveFn = nullfunction watchFn(fn) {currentReactiveFn = fn// 先调用一次函数 , 提醒Proxy的get捕获器需要收集响应式函数了fn()// 收集完成将currentReactiveFn重置currentReactiveFn = null}Proxy中get捕获器具体需要执行的操作:
// 创建一个Proxyconst objProxy = new Proxy(obj, {get: function(target, key, receiver) {const dep = getDepend(target, key)// 拿到全局的currentReactiveFn进行添加dep.addDependFn(currentReactiveFn)return Reflect.get(target, key, receiver)},set: function(target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)// 根据当前对象target和设置的key , 去获取对应的depconst dep = getDepend(target, key)console.log(dep)// 当set捕获器捕获到属性变化时 , 自动去调用notifydep.notify()}})下面测试一下看看效果:
watchFn(function() {console.log('1:我依赖了name属性')console.log(objProxy.name)})watchFn(function() {console.log('2:我依赖了name属性')console.log(objProxy.name)})watchFn(function() {console.log('1:我依赖了age属性')console.log(objProxy.age)})watchFn(function() {console.log('2:我依赖了age属性')console.log(objProxy.age)})console.log('----------以上为初始化执行 , 以下为修改后执行-------------')objProxy.name = 'kobe'objProxy.age = 24
由浅入深,带你用JavaScript实现响应式原理

文章插图
 
5.8.Depend类优化
截止到上面 , 大部分响应式原理已经实现了 , 但是还存在一些小问题需要优化 。
  • 优化一:既然currentReactiveFn可以在全局拿到 , 何不在Depend类中就对它进行收集呢 。改造方法addDependFn;
  • 优化二:如果一个响应式函数中多次访问了某个属性 , 就都会去到Proxy的get捕获器 , 该响应式函数会被重复收集 , 在调用时就会调用多次 。当属性发生变化后 , 依赖这个属性的响应式函数被调用一次就可以了 。改造reactiveFns , 将数组改成Set , Set可以避免元素重复 , 注意添加元素使用add 。
// 将currentReactiveFn放到Depend之前 , 方便其拿到let currentReactiveFn = nullclass Depend {constructor() {// 用于存放响应式函数this.reactiveFns = new Set()}// 用户添加响应式函数addDependFn() {// 先判断一下currentReactiveFn是否有值if (currentReactiveFn) {this.reactiveFns.add(currentReactiveFn)}}// 用于执行响应式函数notify() {this.reactiveFns.forEach(fn => {fn()})}}Proxy中就不用去收集响应式函数了 , 直接调用addDependFn即可:
// 创建一个Proxyconst objProxy = new Proxy(obj, {get: function(target, key, receiver) {const dep = getDepend(target, key)// 直接调用addDepend方法 , 让它去收集dep.addDependFn()return Reflect.get(target, key, receiver)},set: function(target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)// 根据当前对象target和设置的key , 去获取对应的depconst dep = getDepend(target, key)// 当set捕获器捕获到属性变化时 , 自动去调用notifydep.notify()}})5.9.多个对象实现响应式
前面都只讲了一个对象实现响应式的实现 , 如果有多个对象需要实现可响应式呢?将Proxy封装一下 , 外面套一层函数即可 , 调用该函数 , 返回该对象的代理对象 。
function reactive(obj) {return new Proxy(obj, {get: function(target, key, receiver) {const dep = getDepend(target, key)// 直接调用addDepend方法 , 让它去收集dep.addDependFn()return Reflect.get(target, key, receiver)},set: function(target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)// 根据当前对象target和设置的key , 去获取对应的depconst dep = getDepend(target, key)// 当set捕获器捕获到属性变化时 , 自动去调用notifydep.notify()}})}


推荐阅读