面试题答案
一键面试依赖收集的实现
- Object.defineProperty
- 在Vue2.x中,通过
Object.defineProperty
来劫持对象的属性访问和赋值操作。例如:
const data = { name: 'John' }; Object.defineProperty(data, 'name', { get() { // 依赖收集逻辑 console.log('获取name属性'); return value; }, set(newValue) { if (newValue!== value) { value = newValue; // 派发更新逻辑 console.log('name属性值更新'); } } });
- 当访问属性时,会触发
get
方法,在get
方法中进行依赖收集。
- 在Vue2.x中,通过
- Dep类
- Vue内部有一个
Dep
类,它代表一个依赖管理器。每个被劫持的属性都对应一个Dep
实例。 Dep
类有一个subs
数组,用来存储依赖(Watcher实例)。例如:
class Dep { constructor() { this.subs = []; } addSub(sub) { this.subs.push(sub); } notify() { this.subs.forEach(sub => sub.update()); } }
- Vue内部有一个
- Watcher类
Watcher
代表一个观察者,它实例化时会将自身添加到当前正在读取的属性的Dep
中。例如:
class Watcher { constructor(vm, expOrFn, cb) { this.vm = vm; this.cb = cb; // 将当前Watcher实例设置为全局唯一的Watcher实例 Dep.target = this; // 读取数据,触发getter进行依赖收集 this.get(); Dep.target = null; } get() { const vm = this.vm; const value = this.getter.call(vm, vm); return value; } update() { const oldValue = this.value; this.value = this.get(); this.cb.call(this.vm, this.value, oldValue); } }
- 当一个
Watcher
实例读取某个数据属性时,该属性的Dep
会将这个Watcher
添加到subs
数组中,完成依赖收集。
派发更新的流程
- 数据变化触发setter
- 当数据发生变化,例如
data.name = 'Jane'
,会触发set
方法。
- 当数据发生变化,例如
- Dep.notify
- 在
set
方法中,会调用对应的Dep
实例的notify
方法。例如:
set(newValue) { if (newValue!== value) { value = newValue; dep.notify(); } }
- 在
- Watcher.update
Dep
的notify
方法会遍历subs
数组,调用每个Watcher
的update
方法。Watcher
的update
方法会重新获取数据,并执行回调函数(例如更新DOM等操作)。
关键对象与方法
- Dep类
- 关键方法:
addSub
:用于添加依赖(Watcher实例)到subs
数组。notify
:当数据变化时,通知所有依赖(Watcher实例)进行更新。
- 关键方法:
- Watcher类
- 关键方法:
update
:重新获取数据,并执行回调函数,实现更新操作。
- 关键方法:
- Object.defineProperty
- 用于劫持对象属性的访问和赋值操作,是实现依赖收集和派发更新的基础。在Vue3.x中,使用
Proxy
和Reflect
来替代Object.defineProperty
,但核心的依赖收集和派发更新原理类似。
- 用于劫持对象属性的访问和赋值操作,是实现依赖收集和派发更新的基础。在Vue3.x中,使用