尝试vue响应式模拟
响应式我理解为当某个数据发生变化时,任何和其相关的依赖都随之自动发生变化。
体现在数据层面就是:
代码中第一次调用change时,name显然是zxf,当我修改obj.name = 'zdx'时,这个change应该随着name的改变而自动在重新执行一遍。来达到随着数据改变其依赖自动变化的过程
我将vue中 组件的渲染首先就是将各类函数和模板解析 看作收集需要自动响应式变化的(即上面需要重新执行的函数)
// 响应式函数,收集需要根据数据变化而重新执行的函数 let activeReactiveFn = null // 封装一个响应式的函数 接受该属性对应的所有需要响应重新执行的函数 function watch(fn) { activeReactiveFn = fn fn() activeReactiveFn = null }
定义全局变量activeReactiveFn 接受当前存储的依赖fn ,方便后续在依赖收集管理中使用。
实际上组件中往往有很多数据,对象和它们的属性等。所以定义一个类,看作使用这个类统一管理某一个对象的某一个属性的所有响应式函数:
class Depend { constructor() { this.reactiveFns = new Set() // 如果函数中有用到两次key,比如name,那么这个函数会被收集两次;set数据结构的唯一性防止这点。 } // 收集依赖 depend() { if (activeReactiveFn) { this.reactiveFns.add(fn) } } // 通知依赖重新执行 notify() { this.reactiveFns.forEach(fn => { fn() }) } }
- 收集依赖
- 通知所有依赖相关函数重新执行。
然而数据有很多会有不同的对象和其不同的或者相同的属性,我们需要对其做统一的管理。
const per1 = { Name: 'zxf', Age: 23 } const map1 = new Map() map1.set(Name, [nameFN1, nameFn2]) //这里nameFN1是存储的name的依赖 const weakMap = WeakMap() //再定义一个weakMap 将对象和对象中属性和属性的依赖 对应存储 weakMap.set(per1, map1)
如上,利用weakMap和map来存储其中的对象和其属性依赖之间的关系。
依赖关系的关系+收集依赖类 = > 定义一个函数来返回每个属性对应的依赖类
let weakMap = new WeakMap() function collectDepend(obj, key) { let map = weakMap.get(obj) if (!map) { //第一次一般没有数据 那就要为该对象预先建立一个map 存入总的weakMap中 map = new Map() weakMap.set(obj, map) } let depend = map.get(key) if (!depend) { depend = new depend() map.set(key, depend) } return depend }
到此,也只考虑了依赖收集和管理。响应式要求自动来为属性收集依赖,以及自动通知更新重新执行依赖函数。
const proxyOBJ = new Proxy(obj, { get: function (target, key, receiver) { const depend = collectDepend(target, key) depend.depend() return Reflect.get(target, key, receiver) }, set: function (target, key, newVlaue, receiver) { Reflect.set(target, key, newVlaue, receiver)
depend.notify() } })
这样 在proxy的get,set方法中拿到当前对象和属性,通过collectDepend()方法,建立存储该对象某个属性和其依赖关系的map,并建立存储 该对象和这个map之间的weakMap.
这样一来我们就可以为需要成为响应式的对象,每个属性建立其相关的依赖管理。
最后在geter中收集依赖,在setter中通知依赖函数更新即可
最后将proxy封装成对对象的改造函数并模拟将数据转为响应式的整个过程:
let currentFn = null const watchFn = (fn) => { currentFn = fn fn() currentFn = null } // 定义依赖关系对象类 class Depend { constructor() { this.reactiveFns = new Set() } depend() { if (currentFn) { this.reactiveFns.add(currentFn) } } notify() { this.reactiveFns.forEach(fn => { fn() }) } } // 封装某个对象和其相应属性的收集依赖和依赖关系管理的函数 const weakMap = new WeakMap() function collectDepend(obj, key) { let map = weakMap.get(obj) if (!map) { map = new Map() weakMap.set(obj, map) } let depend = map.get(key) if (!depend) { depend = new Depend() map.set(key, depend) } return depend } // 利用代理修改get,set从而监听并修改=> 改造数据成响应式 const reactive = (obj) => { return new Proxy(obj, { get: function (target, key, receiver) { const depend = collectDepend(target, key) //建立依赖关系关系 depend.depend() //收集当前属性依赖 return Reflect.get(target, key, receiver) }, set: function (target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) //需要先set 更新值,再通知响应式做出依赖的更新 const depend = collectDepend(target, key) //建立依赖关系关系 depend.notify() //通知要重新执行依赖相关函数 响应更新更新 } }) } // 至此,可以尝试通过以上响应函数改造后的结果。 let per1 = { name: 'zxf', age: 23 } const proxyObj = reactive(per1) // 模拟组件数据的使用 watchFn(() => { console.log(proxyObj.name) }) // 模拟数据发生变化 proxyObj.name = 'zdx'
至此,当我修改proxyObj.name时,响应式函数监听到并通知更新依赖,再一次执行了我们传入console.log(proxyObj.name)。达到监听并响应数据更改从而更新组件页面数据的目的。