100行代码理解vue中的dep,watch,computed机制

const data = https://www.it610.com/article/{ flag: false, firstName:'Zheng', lastName: 'Yimeng' }const computed = { name() { if (!data.flag) { return '你拿不到' } return data.firstName + ' ' + data.lastName }, }function observe(obj) { const keys = Object.keys(obj) /** 需要注意这里,我并没有对传入的 obj 本身做响应式处理,是为了简化代码 (vue 源代码对传入的对象也做了处理) */ keys.forEach(key => { observer(obj, key) }) }function observer(obj, key) { let value = https://www.it610.com/article/obj[key] //?1 为什么需要这个东西 let dep = new Dep() // 每个属性都有一个自身的 dep。 Object.defineProperty(obj, key, { get() { if (Dep.target) { console.log(`收集依赖 ${key}`) Dep.target.addDep(dep) } return value //?1 因为在这里写 obj[key] 的时候相当于重新访问这个 key,会再次触发 get 方法,进入死循环 }, set(newVal) { if (value === newVal) return value = newVal if (dep.subs.length) { console.log(`${key} 改变了,我要更新它记录的 watcher 了`) dep.notify() } } }) }function initComputed(computed) { /** 注意我这里搞 computed 重新拆来搞只是为了让大家看明白,vue 源代码和我不一样,但是原理类似 */ const watchers = {} const keys = Object.keys(computed) keys.forEach(key => { watchers[key] = new Watcher(computed[key]) }) return watchers }function Dep () { this.subs = [] }Dep.prototype.notify = function() { this.subs.forEach(watcher => watcher.update()) }function Watcher(func) { this.get = func this.deps = [] Dep.target = this this.value = https://www.it610.com/article/this.get() // 函数体是 { return data.firstName +' ' + data.lastName },所以会调用 data.firstName 和 data.lastName 的 get 方法 Dep.target = null }Watcher.prototype.addDep = function(dep) { // 这里为什么需要记录 dep? 因为:假设现在 flag 为 true,那么我们的 computed.name 收集依赖的时候收集到了 data.flag, data.firstName, data.lastName,这三个属性每个都有一个 dep,修改其中一个都会重新调用 dep.notify() 从而更新 name 值, 但是当我们修改其中的一个属性的时候,这个收集的依赖其实就已经改变了,比如修改了 flag = false,再修改 data.firstName 和 data.lastName 的时候,就不应该再重新求 computed.name 值了,因为走不到那一步,那么 data.firstName 和 data.lastName 的 dep 就应该为空,这样在修改 data.firstName 和 data.lastName 的时候因为他们的 dep 为空就不会重新求 computed.name 值了 this.deps.push(dep) dep.subs.push(this) // 到这里的时候,还记得吗?自身属性的 watcher 就被记录下来了 }Watcher.prototype.update = function() { this.deps.forEach(dep => dep.subs = []) // 在这里把该 计算属性 对应的每个 依赖属性 的 dep 都清空 Dep.target = this let value = https://www.it610.com/article/this.get() // 这里调用 get 后重新收集依赖 Dep.target = null if (this.value !== value) { this.value = value // value改变,渲染页面 } }function MyVue({data, computed}) { observe(data) this.watchers = initComputed(computed) // 这里把属性简单地绑给实例本身 Object.keys(this.watchers).forEach(key => { // 这里 computed 我假设不让手动修改它,所以不作处理 Object.defineProperty(this, key, { get() { return this.watchers[key].value } }) }) Object.keys(data).forEach(key => { Object.defineProperty(this, key, { get() { return data[key] }, set(newVal) { data[key] = newVal } }) }) }window.vm = new MyVue({data, computed})

初始化data
observe(data)

function observe(obj) { const keys = Object.keys(obj) /** 需要注意这里,我并没有对传入的 obj 本身做响应式处理,是为了简化代码 (vue 源代码对传入的对象也做了处理) */ keys.forEach(key => { observer(obj, key) }) }function observer(obj, key) { let value = https://www.it610.com/article/obj[key] //存储旧值 let dep = new Dep() // 每个属性都有一个自身的 dep。 Object.defineProperty(obj, key, { get() { if (Dep.target) { console.log(`收集依赖 ${key}`) Dep.target.addDep(dep) } return value //?1 因为在这里写 obj[key] 的时候相当于重新访问这个 key,会再次触发 get 方法,进入死循环 }, set(newVal) { if (value === newVal) return value = newVal if (dep.subs.length) { console.log(`${key} 改变了,我要更新它记录的 watcher 了`) dep.notify() } } }) }

为每个data属性生成一个dep对象,在属性的get方法中,触发watch.addDep方法完成dep和watch的相互收集。在set方法中通知watch
Dep对象
subs:存储watch
notify:属性更新了提醒相关watch
function Dep () { this.subs = [] }Dep.prototype.notify = function() { this.subs.forEach(watcher => watcher.update()) }

初始化computed
this.watchers = initComputed(computed)

function initComputed(computed) { /** 注意我这里搞 computed 重新拆来搞只是为了让大家看明白,vue 源代码和我不一样,但是原理类似 */ const watchers = {} const keys = Object.keys(computed) keys.forEach(key => { watchers[key] = new Watcher(computed[key]) }) return watchers }

为每个computed生成一个watch。
watch
function Watcher(func) { this.get = func this.deps = [] Dep.target = this this.value = https://www.it610.com/article/this.get() // 函数体是 { return data.firstName +' ' + data.lastName },所以会调用 data.firstName 和 data.lastName 的 get 方法 Dep.target = null } Watcher.prototype.addDep = function(dep) { // 这里为什么需要记录 dep? 因为:假设现在 flag 为 true,那么我们的 computed.name 收集依赖的时候收集到了 data.flag, data.firstName, data.lastName,这三个属性每个都有一个 dep,修改其中一个都会重新调用 dep.notify() 从而更新 name 值, 但是当我们修改其中的一个属性的时候,这个收集的依赖其实就已经改变了,比如修改了 flag = false,再修改 data.firstName 和 data.lastName 的时候,就不应该再重新求 computed.name 值了,因为走不到那一步,那么 data.firstName 和 data.lastName 的 dep 就应该为空,这样在修改 data.firstName 和 data.lastName 的时候因为他们的 dep 为空就不会重新求 computed.name 值了 this.deps.push(dep) dep.subs.push(this) // 到这里的时候,还记得吗?自身属性的 watcher 就被记录下来了 }Watcher.prototype.update = function() { this.deps.forEach(dep => dep.subs = []) // 在这里把该 计算属性 对应的每个 依赖属性 的 dep 都清空 Dep.target = this let value = https://www.it610.com/article/this.get() // 这里调用 get 后重新收集依赖 Dep.target = null if (this.value !== value) { this.value = value // value改变,渲染页面 } }

【100行代码理解vue中的dep,watch,computed机制】deps:存储相关依赖dep
1、构造函数中调用了this.get方法就会触发相关属性的get方法。进而触发watch.addDep方法,dep和watch相互收集
2、依赖更新了,watch.update触发。清空上次的依赖数组;重新计算computed值;重新构建依赖数组

    推荐阅读