import Dep, { pushTarget, popTarget } from './dep' // 解析链式引用,parsePath(a.b.c) 返回一个函数: obj => obj.a.b.c const bailRE = /[^\w.$]/ function parsePath(path: string) { if (bailRE.test(path)) { return } const segments = path.split('.') return function(obj?: any) { for (let i = 0; i < segments.length; i++) { if (!obj) return obj = obj[segments[i]] } return obj } } // 判断某项是否在数组中 function isHas (arr: Array, item: any) { if (arr.some((element) => { if (item.id === element.id) return true })) { return true } else { return false } } let id = 0 export default class Watcher { id: number obj: Object callback: Function deps: Array newDeps: Array getter: Function value: any constructor(obj: Object, expOrFn: string | Function, callback: Function) { this.id = id++ this.obj = obj this.callback = callback // 每次数据变化都会重新执行 this.getter() ,并再次触发数据的 getters,所以数据的依赖会被重新收集 // 在重新收集的过程中,可能会存在一些可以复用的 dep ,所以分别用两个数组来保存所有的 dep // 复用的目的是减少 dep.addWatcher(this) 和 dep.removeWatcher(this) 的多次操作 this.deps = [] // 表示上一次添加的 Dep 实例数组,以下简称:旧 deps this.newDeps = [] // 表示新添加的 Dep 实例数组,以下简称:新 deps if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) this.getter = function() {} } this.value = this.get() } // 添加监听,并取值 get() { pushTarget(this) // 对用到的属性进行监听 const value = this.getter.call(this.obj, this.obj) popTarget() this.cleanupDeps() return value } // 更新值,并触发监听 update() { const value = this.get() if (value !== this.value) { const oldValue = this.value this.value = value this.callback.call(this.obj, value, oldValue) } } // 添加一个依赖(保证同一数据不会被添加多次) addDep(dep: Dep) { if (!isHas(this.newDeps, dep)) { // 如果新 deps 中不存在该 dep ,将其添加新 deps this.newDeps.push(dep) // 如果旧 deps 中不存在该 dep ,将其添加旧 deps // 如果已经有了就不需要添加,此时就实现了 dep 的复用 if (!isHas(this.deps, dep)) dep.addWatcher(this) } } /** * 依赖管理(把多余的 watcher 移除) */ cleanupDeps() { this.deps.forEach(dep => { // 如果旧 deps 中存在新 deps 中不存在的 dep,需要将其移除 if (!isHas(this.newDeps, dep)) dep.removeWatcher(this) }) // 将旧 deps 和新 deps 交换,最后清空旧 deps(长江后浪推前浪,前浪死在沙滩上) /** * 这里不能写为 * this.deps = this.newDeps * this.newDeps.length = 0 * 因为引用类型赋值导致 this.deps 和 this.newDeps 指向统一地址 * 当清空 this.newDeps 同时也意味着 this.deps 被清空 * 所以需要引入一个中间变量 tmp */ let tmp = this.deps // 开辟一个新指针 tmp,指向旧 deps this.deps = this.newDeps // 将 this.deps 指针指向新添加的 Dep 实例数组 this.newDeps = tmp // 将 this.newDeps 指针指向旧 deps this.newDeps.length = 0 // 清空旧 deps } // watcher 拆卸自己:通知所有的 dep 移除自己(调用 dep.removeWatcher(watcher) 移除 wather) teardown() { let i = this.deps.length while (i--) this.deps[i].removeWatcher(this) this.deps = [] } }