let m = 20 console.log(m) console.log(m*2) m = 40; 数据发生改变
// 对象的响应式 const obj = { name: 'deyang', age: 24 } console.log('hello world') console.log(obj.name) obj.name = "superman"
对象的属性发生改变,我们希望涉及到该属性的代码能够重新执行,因此最好放到一个函数里面,
function foo() { const newName = obj.name console.log('hello world') console.log(obj.name) }
现在的问题就是当我的name属性发生改变的时候,foo函数能够重新执行。如果此时有另外一个函数 bar,但是这个函数我们不需要有响应式。
function bar() { console.log('普通的其他函数') console.log('这个函数不需要有任何的响应式'); }
所以我们的思路是封装一个响应式函数,将需要响应式的函数传递进去变成响应式。哪些函数需要响应式呢?产生了依赖的函数需要变成响应式。按照这个思路我们来完成响应式原理
// 封装一个响应式函数 let reactiveFns = [] // 定义存储响应式函数的数组 function watchFn(fn) { reactiveFns.push(fn) // 将产生依赖的函数存入响应式 } // 响应式对象 const obj = { name: 'deyang', age: 24 } // 将依赖产生的函数变成响应式 watchFn(function foo() { const newName = obj.name console.log('hello world') console.log(obj.name) }) watchFn(function demo() { console.log(obj.name, 'demo-------------') }) function bar() { console.log('普通的其他函数') console.log('这个函数不需要有任何的响应式'); } obj.name = "chendeyang" // 对象的属性发生改变,触发执行响应式函数 reactiveFns.forEach(fn => { fn() })
目前响应式第一步已经实现,当对象 name 发生改变,循环执行响应式数组内的函数并执行
但是目前用一个数组收集的只是 name 一个属性的依赖,但是obj 还有 age 属性的依赖呢,开发中又有其他的类,因此需要用一个有状态的工具类来收集依赖,当数据发生改变通过这个类来触发依赖
封装一个 Depend 的类,来收集和管理依赖,通过 addDepend 添加依赖,当依赖值发生改变的时候,通过 notify 来触发依赖
class Depend { constructor() { this.reactiveFns = [] } addDepend(reactiveFn) { this.reactiveFns.push(reactiveFn) } notify() { this.reactiveFns.forEach(fn => { fn() }) } } // 封装一个响应式函数 const depend = new Depend(); function watch(fn) { depend.addDepend(fn) } watchFn(function demo() { console.log(obj.name, 'demo-------------') }) function bar() { console.log('普通的其他函数') console.log('这个函数不需要有任何的响应式'); } obj.name = "chendeyang" reactiveFns.forEach(fn => { fn() })
我们目前的代码还是手动调用 depend.notify() 来触发依赖
我们想要当对象属性发生变化的时候自动触发依赖,就要监听对象属性的改变,前面的几篇讲了两个方法 proxy / Obejct.defineProperty,先实现vue3的响应式,当我们用 proxy 代理的时候,后续就不要对原来的 obj 对象进行操作了,而是通过代理对象。
// 对象的响应式 const obj = { name: 'deyang', // depend对象 age: 24 // depend 对象 } // 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2) const objProxy = new Proxy(obj, { get(target, key, receiver) { return Reflect.get(target, key, receiver) }, set(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) } }) watchFn(function foo() { const newName = objProxy.name console.log('欢迎来到卢本伟广场') console.log(objProxy.name) }) objProxy.name = "superman"
当我们对 代理对象的name进行赋值的时候,就会进入到 set捕获器,我们就可以在 set 中进行依赖的触发
class Depend { constructor() { this.reactiveFns = [] } addDepend(reactiveFn) { this.reactiveFns.push(reactiveFn) } notify() { this.reactiveFns.forEach(fn => { fn() }) } } // 封装一个响应式函数 const depend = new Depend() function watchFn(fn) { depend.reactiveFns.push(fn) } // 对象的响应式 const obj = { name: 'deyang', // depend对象 age: 24 // depend 对象 } // 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2) const objProxy = new Proxy(obj, { get(target, key, receiver) { return Reflect.get(target, key, receiver) }, set(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) depend.notify() } }) watchFn(function foo() { const newName = objProxy.name console.log('hello world') console.log(objProxy.name) }) objProxy.name = "super" objProxy.name = "man" objProxy.name = "卢本伟"
原来name 发生改变,我们需要手动触发三次,现在就完成了自动触发
针对上面的代码,如果我们产生了 age属性的依赖
watchFn(function () { console.log(obj.age, '监听age的变化...1') }) watchFn(function () { console.log(obj.age, '监听age的变化...2') }) objProxy.age = 111
在 age 属性发生变化的时候,它自动将 name 的依赖也自动触发了,这显然不是我们想看到的,如果另外还有一个 info 对象,以及info 对象的依赖,当一个依赖属性发生改变了,所有的依赖都会出发。因此我们需要对依赖进行管理。
正确的思路应该是:我们将依赖按照对象划分为 obj 和 info, 然后又根据属性划分 obj 和 info 下面的依赖
我们可以用到 ES6 的Map结构,为每一个响应式对象创建一个 map
const objMap = new Map() objMap.set("name", "nameDepend") objMap.set("age", "ageDepend") const infoMap = new Map() infoMap.set("address", "上海市")
然后我们将创建的map对象用 WeakMap 保存起来
const targetMap = new WeakMap() targetMap.set(obj, objMap) targetMap.set(info, infoMap)
这样做的好处是,如果 obj.name 发生改变了就可以通过 targetMap.get(obj) 获取 obj对象的 objMap,然后通过objMap.get(name) 获取存储的 name依赖,然后触发依赖
// obj.name 发生改变 const depend = targetMap.get(obj).get(name); depend.notify()
按照上面的思路封装一个获取 depend 的函数
// 封装一个获取 depend 的函数 const targetMap = new WeakMap() function getDepend(target, key) { // 根据 target 对象获取 map 的过程 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 } // 对象的响应式 const obj = { name: 'why', // depend对象 age: 24 // depend 对象 } // 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2) const objProxy = new Proxy(obj, { get(target, key, receiver) { return Reflect.get(target, key, receiver) }, set(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) // depend.notify() const depend = getDepend(target, key) console.log(depend.reactiveFns) // 此时打印四次 [] depend.notify() } }) watchFn(function foo() { const newName = objProxy.name console.log('hello world') console.log(objProxy.name) }) objProxy.name = "chen" objProxy.name = "de" objProxy.name = "yang" objProxy.age = 111
但是此时触发依赖前,输出一下获取的依赖,其实都是空的,因为我们还没有正确的收集依赖
我们之前将传入的 watchFn() 的函数都放在了一个depend中,这显然是不对的,应该根据不同对象划分不同的depend,传入不同的depend
// 对象的响应式 const obj = { name: 'why', // depend对象 age: 24 // depend 对象 } const info = { address: '上海' // depend } ..... // 用到了obj的属性就放到 obj 的 depend watchFn(function() { console.log('欢迎来到卢本伟广场') console.log(objProxy.name) }) // 用到了info的属性就放到 obj 的 depend watchFn(function() { console.log(infoProxy.address) })
上面代码当运行到74行的时候,会走到 objProxy的get方法,那么我们就可以在这个捕获器里面获取depend,然后收集依赖
class Depend { constructor() { this.reactiveFns = [] } addDepend(reactiveFn) { this.reactiveFns.push(reactiveFn) } notify() { this.reactiveFns.forEach(fn => { fn() }) } } // 封装一个响应式函数 let activeReactiveFn = null function watchFn(fn) { activeReactiveFn = fn fn() activeReactiveFn = null } // 封装一个获取 depend 的函数 const targetMap = new WeakMap() function getDepend(target, key) { // 根据 target 对象获取 map 的过程 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 } // 对象的响应式 const obj = { name: 'deyang', // depend对象 age: 24 // depend 对象 } // 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2) const objProxy = new Proxy(obj, { get(target, key, receiver) { // 根据对应target获取对应depend const depend = getDepend(target, key) // depend中添加响应式函数 depend.addDepend(activeReactiveFn) return Reflect.get(target, key, receiver) }, set(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) // depend.notify() const depend = getDepend(target, key) depend.notify() } }) watchFn(function foo() { console.log(objProxy.name, 'name depend 1') }) watchFn(function foo() { console.log(objProxy.name, 'name depend 2') }) watchFn(function () { console.log(objProxy.age, 'age depend 1') }) watchFn(function () { console.log(objProxy.age, 'age depend 2') }) console.log('上面是默认执行的代码-------------------------') objProxy.name = '李银河' // objProxy.age = 10
目前就可以正确的收集依赖触发依赖,只有对应属性发生改变的时候才会触发依赖,
// 保存当前需要收集的响应式函数 let activeReactiveFn = null /** * Depend优化: * 1.depend方法 * 2.使用Set来保存依赖函数,而不是使用数组[] */ class Depend { constructor() { // this.reactiveFns = [] this.reactiveFns = new Set() } depend() { if (activeReactiveFn) { this.reactiveFns.add(activeReactiveFn) } } notify() { this.reactiveFns.forEach(fn => { fn() }) } } // 封装一个响应式函数 function watchFn(fn) { activeReactiveFn = fn fn() activeReactiveFn = null } // 封装一个获取 depend 的函数 const targetMap = new WeakMap() function getDepend(target, key) { // 根据 target 对象获取 map 的过程 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 } // 对象的响应式 const obj = { name: 'deyang', // depend对象 age: 24 // depend 对象 } // 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2) const objProxy = new Proxy(obj, { get(target, key, receiver) { // 根绝对应target获取对应depend const depend = getDepend(target, key) // depend中添加响应式函数 // depend.addDepend(activeReactiveFn) depend.depend() return Reflect.get(target, key, receiver) }, set(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) // depend.notify() const depend = getDepend(target, key) depend.notify() } }) watchFn(() => { console.log(objProxy.name, '--------') console.log(objProxy.name, '++++++++') console.log(objProxy.name, '++++++++') }) objProxy.name = 'haha'
proxy
// 保存当前需要收集的响应式函数 let activeReactiveFn = null class Depend { constructor() { // this.reactiveFns = [] this.reactiveFns = new Set() } depend() { if (activeReactiveFn) { this.reactiveFns.add(activeReactiveFn) } } notify() { this.reactiveFns.forEach(fn => { fn() }) } } // 封装一个响应式函数 function watchFn(fn) { activeReactiveFn = fn fn() activeReactiveFn = null } // 封装一个获取 depend 的函数 const targetMap = new WeakMap() function getDepend(target, key) { // 根据 target 对象获取 map 的过程 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 } function defineReactive(obj) { return new Proxy(obj, { get(target, key, receiver) { // 根绝对应target获取对应depend const depend = getDepend(target, key) depend.depend() return Reflect.get(target, key, receiver) }, set(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) // depend.notify() const depend = getDepend(target, key) depend.notify() } }) } // 对象的响应式 // 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2) const objProxy = defineReactive({ name: 'chendeyang', // depend对象 age: 24 // depend 对象 }) const infoProxy = defineReactive({ address: '上海市', height: 1.88 }) watchFn(() => { console.log(infoProxy.address) }) infoProxy.address = '武汉市'
Object.defineProperty
// 保存当前需要收集的响应式函数 let activeReactiveFn = null class Depend { constructor() { // this.reactiveFns = [] this.reactiveFns = new Set() } depend() { if (activeReactiveFn) { this.reactiveFns.add(activeReactiveFn) } } notify() { this.reactiveFns.forEach(fn => { fn() }) } } // 封装一个响应式函数 function watchFn(fn) { activeReactiveFn = fn fn() activeReactiveFn = null } // 封装一个获取 depend 的函数 const targetMap = new WeakMap() function getDepend(target, key) { // 根据 target 对象获取 map 的过程 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 } function defineReactive(obj) { Object.keys(obj).forEach(key => { let value = obj[key] Object.defineProperty(obj, key, { get() { const depend = getDepend(obj, key) depend.depend() return value }, set(newValue) { value = newValue const depend = getDepend(obj, key) depend.notify() } }) }) return obj } // 对象的响应式 // 监听对象的变化:proxy(vue3) / Object.defineProrperty(vue2) const obj = defineReactive({ name: 'chendeyang', // depend对象 age: 24 // depend 对象 }) const info = defineReactive({ address: '上海市', height: 1.88 }) watchFn(() => { console.log(info.address) }) info.address = '武汉市'