收集依赖此文转载自乐字节 前面我们已经将数据变成了响应式数据,但是怎么让用户定义的方法可以在这些数据改变时调用呢? 这就要用到 Watcher,new Watcher 时传入的参数里会有个 callback,这个 callback 就可以是用户定义的方法,通过 watcher 那些响应式数据,当数据改变时,调用 callback。 那么为什么 watcher 的数据改变时能够调用 callback 呢? 这就涉及到依赖的收集,首先这些数据已经是 observe 处理了,也就是说已经是响应式的了。在 Watcher 的构造函数中会去获取要订阅的数据的值,这就会触发数据的 getter,一旦触发 getter 就会把这个 watcher 实例收集到一个数组 subs 里,一旦这个数据被改动,就会触发 setter,然后在 setter 里会循环 subs 数组,一个个去通知,执行 update 方法,通过 update 方法里最终触发 callback。 依赖是什么?
需要用到数据的地方称为依赖。在 vue2.x 中,用到数据的组件是依赖。当数据变化时通知组件,在组件内通过虚拟 dom 进行 diff 算法 在 getter 中收集依赖,在 setter 中触发依赖
Dep 类 Dep 类用来封装依赖收集的代码,管理依赖 // Dep.js export default class Dep { constructor(arg) { // 用数组存储自己的订阅者, 数组里是 Watcher 实例 this.subs = [] }
// 添加订阅 addSub(sub) { this.subs.push(sub) }
// 添加依赖 depend() { // Dep.target 就是我们指定的一个全局唯一位置,换成 window.target 也一样 if (Dep.target) { this.addSub(Dep.target) } }
// 通知更新 notify() { const subs = this.subs.slice() // 浅克隆 subs.forEach(item => { item.update() }) } } 复制代码 每个 Observer 实例中都有一个 Dep 的实例 在 Observer 类的constructor 函数中 const dep = new Dep() // Observer.js ... import Dep from './Dep.js' export default class Observer { constructor(value) { this.dep = new Dep() // 本次笔记的案例中,这里其实不写也可以 ... } ... } 复制代码 还有个地方也会创建 Dep 实例,就是在 defineReactive 里 目的是在于可以在被侦测的对象 setter 时去发通知 dep.notify() // defineReactive.js import Dep from './Dep.js' export default function defineReactive(data, key, value) { const dep = new Dep() ... Object.defineProperty(data, key, { ... set(newValue) { ... // 在 setter 中触发依赖 dep.notify() } }) } 复制代码 Dep 使用发布订阅模式,当数据发生变化时,会循环依赖列表,把所有 Watcher 都通知一遍。 这样,一旦去修改 obj,比如 obj.b = 3,就会执行 Dep 的 notify 方法。当然,通过 7 种能改变数组本身的方法改变数组时,也需要能够通知,所以在 array.js 改写 7 中方法时也加上 ob.dep.notify() // array.js ... methodsCouldChange.forEach(item => { ... def(arrayMethods, item, function() { ... ob.dep.notify() ... }, false) }) ... 复制代码 Watcher 类 先说目的 我们最终目的是在 index.js 新建一个 watcher 类的实例去监控我们指定的对象的指定的属性,并希望在 new Watcher() 的第 3 个参数,乐字节回调函数里得到对象属性修改前后的值,这样就可以去做一些我们想做的事情了。 // index.js import observe from './observe.js' import Watcher from './Watcher.js'
let obj = { a: { m: { n: 1 } } } observe(obj) new Watcher(obj, 'a.m.n', (val, oldValue) => { console.log('watcher', val, oldValue) }) obj.a.m.n = 2 复制代码 期望得的到结果是
也就是说只要我 new 了一个 Watcher 实例,并把想要监控的属性(a.m.n)和对象(obj)传进去,那么在 Watcher 的第 3 个参数,也就是个回调函数里就能得到 obj.a.m.n 的新旧属性,并能继续做一些事情,比如进行 diff 算法等等。下面开始书写 Watcher 类: 新建 Watcher.js 文件 // Watcher.js import Dep from './Dep.js' let uid = 0 export default class Watcher { constructor(target, expression, callback) { this.id = uid++ // 让每个 watcher 实例有一个自己的 id this.target = target // target 为新建实例时传入的要监控的对象(obj) this.getter = parsePath(expression) // getter 会是一个函数, 在下面定义的 get 里调用 this.callback = callback // callback 就是传入的回调函数 this.val = this.get() // 获取对象 target 的 expression 属性的值 }
// 数据更新触发 update() { this.run() }
get() { // 将 Dep.target 赋值为 new 的这个 Watcher 实例本身,代表进入依赖收集阶段 Dep.target = this const obj = this.target
let value
try {
/*
注意,一旦这里去获取 obj 的 expression 属性的值,
因为 obj 已经被 observe 了,所以就会触发 defineReactive,
Object.defineProperty 里的 get() 就会被触发
*/
value = this.getter(obj)
} finally { // 在 try 语句块之后执行, 无论是否有异常抛出或捕获都将执行
Dep.target = null // 退出依赖收集
}
return value
}
ps需要自学视频可以关注b站——BV1zK4y137wr?p=3&spm_id_from=pageDriver