当前存在的问题:一个对象中的所有属性使用同一个Depend对象, 导致无法区分不同属性的依赖关系(是否发生变化)。例如: 例如当obj.name或obj.age变化时,都会通知所有依赖函数
一. 数据结构缺陷
-
- 对于包含name和age属性的obj对象,使用的是同一个Depend对象会同时收集两个属性的reactiveFns,会造成无法区分是那个属性更新了。
-
- 数据结构图如下:
二. 将数据结构修改多级层次,可以实现自动收集依赖
-
- 实际开发中会不同的对象,另外会有不同的属性需要管理,可以使用一种数据结构来管理不同对象的不同依赖关系;
-
- 通过WeakMap来管理响应式的数据依赖:
- 通过WeakMap来管理响应式的数据依赖:
-
- 数据结构文字描述:
objWeakMap对象 -> obj对象 -> map对象 -> key -> dep对象
- 3.1.
WeakMap层
:以响应式对象为key
(如obj/user),值为第二层Map
- 3.2.
Map层
:以对象属性名为key
(如name/age),值为对应Dep对象
- 3.3.
Dep层
:存储该属性
对应的所有reactiveFns
- 数据结构文字描述:
-
- 对象依赖管理的实现:
- 4.1. dep对象管理的数据结构如下:
// 封装一个函数:负责通过obj的key获取对应的Depend对象 const objMap = new WeakMap() // WeakMap弱引用 function getDepend (obj, key) { // 1.根据对象obj,找到对应的map对象 // obj对象 -> map对象 -> dep对象 // objMap对象 -> obj对象 -> map对象 -> key -> dep对象 let map = objMap.get(obj) if(!map) { map = new Map() objMap.set(obj, map) } // 2.根据key,找到对应的depend对象 let dep = map.get(key) if(!dep) { dep = new Depend(); map.set(key, dep) } return dep }
- 4.2. 通过代理对象进行数据监听
// 监听属性变化数据劫持 // 方案一:Object.defineProperty -> Vue2 Object.keys(obj).forEach(key => { let value = obj[key]; Object.defineProperty(obj, key, { set: function(newValue) { value = newValue; // 更新属性值时触发 // 调用dep.notify()通知所有依赖函数 const dep = getDepend(obj, key) dep.notify() }, get: function() { // 拿到obj -> key // console.log('get函数中:', obj, key); // 找到对应的obj对象的key对应的dep对象 // 自动收集依赖 const dep = getDepend(obj, key) dep.addDepend(reactiveFn) // obj.name -> dep对象 reactiveFns // obj.age -> dep对象 reactiveFns return value; } }) })
5.自动收集原理:
- 设置全局reactiveFn自由变量存储当前函数
- 执行响应式函数如果访问属性触发getter收集依赖
- 执行完成后清空reactiveFn
- 示例代码如下:
let reactiveFn = null // 自由变量 function watchFn (fn) { reactiveFn = fn fn() reactiveFn = null }
-
- 完整代码:
/** * 1. dep对象数据结构的管理 * 每一个对象的每一个属性都会对应一个dep对象 * 同一个对象的多个属性的dep对象是存放一个map对象中 * 多个对象的map对象,会被存放到一个objMap的对象中 * 2. 依赖收集:当执行get函数时,自动的添加fn函数 */ // 实际开发中,响应式对象肯定是不止一个对象的,使用类单独来管理一个对象所有的依赖 class Depend { constructor() { this.reactiveFns = []; } depend () { if(reactiveFn) { this.reactiveFns.add(reactiveFn) } } addDepend (fn) { if(fn) { this.reactiveFns.push(fn); } } notify () { this.reactiveFns.forEach(fn => { fn() }) } } const obj = { name: 'why', age: 18 } // 封装一个函数:负责通过obj的key获取对应的Depend对象 const objMap = new WeakMap() // WeakMap弱引用 function getDepend (obj, key) { // 1.根据对象obj,找到对应的map对象 // obj对象 -> map对象 -> dep对象 // objWeakMap对象 -> obj对象 -> map对象 -> key -> dep对象 let map = objMap.get(obj) if(!map) { map = new Map() objMap.set(obj, map) } // 2.根据key,找到对应的depend对象 let dep = map.get(key) if(!dep) { dep = new Depend(); map.set(key, dep) } return dep } // 监听属性变化数据劫持 // 方案一:Object.defineProperty -> Vue2 Object.keys(obj).forEach(key => { let value = obj[key]; Object.defineProperty(obj, key, { set: function(newValue) { value = newValue; // 更新属性值时触发 // 调用dep.notify()通知所有依赖函数 const dep = getDepend(obj, key) dep.notify() }, get: function() { // 拿到obj -> key // console.log('get函数中:', obj, key); // 找到对应的obj对象的key对应的dep对象 // 自动收集依赖关系 const dep = getDepend(obj, key) // 两种写法:一种需要响应式函数参数,一种直接写到类方法里面 // dep.addDepend(reactiveFn) dep.depend() // obj.name -> dep对象 reactiveFns // obj.age -> dep对象 reactiveFns return value; } }) }) // 方案二:new Proxy() -> Vue3(暂时不写) // 设置一个专门执行响应式函数的一个函数 let reactiveFn = null // 自由变量 function watchFn (fn) { reactiveFn = fn fn() reactiveFn = null } // 自动收集依赖:只要把foo函数传入watchFn()中,不管是否发生变化都添加到dep中的reactiveFns去,会存在一个问题 // 当name发生变化时只需要通知foo函数, age发生变化时也通知foo和bar函数,修改obj.name时发现foo和bar函数都执行了,这是不对的 // name发生变化时只通知name发生变化的函数,但是obj对象对应的是同一个Depend对象,会造成对应name属性和age属性的依赖都放到一个Depend对象中,造成无法区分那个属性依赖发生变化 // obj对象 // age发生变化时只通知age发生变化的函数 // 响应式函数 watchFn(function foo () { console.log('foo function'); console.log('foo: ', obj.name); console.log('foo: ', obj.age); }) watchFn(function bar () { console.log('bar: ', obj.age + 10); console.log('bar function'); }) // 修改obj的属性 console.log('age发生变化时----------------------------------------'); // obj.name = 'kobe' obj.age = 18
-
- 完整数据结构图如下