const data = { 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 = 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 = 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 = 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 = 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 = 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 = this.get() // 这里调用 get 后从新收集依赖 Dep.target = null if (this.value !== value) { this.value = value // value扭转,渲染页面 }}
deps:存储相干依赖dep
1、构造函数中调用了this.get办法就会触发相干属性的get办法。进而触发watch.addDep办法,dep和watch互相收集
2、依赖更新了,watch.update触发。清空上次的依赖数组;从新计算computed值;从新构建依赖数组