Vue 2 的响应式系统通过对象劫持、依赖收集、和更新通知机制来实现数据驱动视图的更新。以下是实现的大致步骤(当然具体的实现复杂得多):
一、对象劫持(Object.defineProperty)
1. 定义响应式对象
使用 Object.defineProperty 对对象的每个属性进行劫持。Vue 2 会为每个属性定义 getter 和 setter,以便在属性被访问或修改时触发相应的逻辑。
function defineReactive(obj, key, val) {
const dep = new Dep(); // 用于管理依赖
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
dep.addSub(Dep.target); // 添加当前 watcher 作为依赖
}
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
dep.notify(); // 通知所有依赖更新
}
});
}
2. 递归劫持嵌套对象
如果属性的值是对象,Vue 2 需要递归地将这些对象转化为响应式对象。通过遍历对象的所有属性来实现。
function observe(value) {
if (!value || typeof value !== 'object') return;
return new Observer(value);
}
class Observer {
constructor(value) {
this.value = value;
this.walk(value);
}
walk(obj) {
Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));
}
}
二、依赖收集与通知
1. 依赖管理(Dep 类)
Dep 类用于管理所有依赖(即 Watcher 实例)。每当一个属性被访问时,它会将当前 Watcher 实例添加到依赖列表中。当属性值变化时,Dep 会通知所有依赖进行更新。
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
2. Watcher 类
Watcher 类是 Vue 2 响应式系统的核心。它用于接收数据变化通知并更新视图。
class Watcher {
constructor(updateFn) {
this.update = updateFn;
Dep.target = this;
// 触发 getter 以便收集依赖
this.value = this.get();
Dep.target = null;
}
get() {
// 访问数据属性触发 getter
}
update() {
// 数据变化时的处理逻辑
this.get();
}
}
三、计算属性与侦听器
、1. 计算属性(Computed Properties)
计算属性是基于响应式数据的缓存值。Vue 2 会自动缓存计算属性的结果,直到它依赖的响应式数据发生变化。
computed: {
doubledValue() {
return this.value * 2;
}
}
在实现上,计算属性会创建一个专门的 Watcher 实例,并在依赖的属性发生变化时重新计算其值。
2. 侦听器(Watchers)
侦听器用于观察数据变化并执行指定的回调函数。
watch: {
value(newValue, oldValue) {
// 当 value 发生变化时执行的逻辑
}
}
四、模板编译
Vue 2 的模板编译过程将模板转换成渲染函数,并利用响应式系统来实现数据驱动的视图更新。渲染函数会访问组件的数据属性,触发依赖收集和视图更新。
function compileTemplate(template) {
// 编译模板为渲染函数
return function render() {
// 渲染逻辑
};
}
在渲染过程中,模板中的数据绑定会触发属性的 getter,自动收集依赖。当数据变化时,Watcher 会重新计算并更新视图。
五、响应式数据的访问与更新
1. 访问数据
当访问一个响应式属性时,getter 会被触发,当前的 Watcher 会被添加到依赖列表中。
function get() {
// 访问数据属性触发 getter
return this.value;
}
2. 更新数据
当设置响应式属性的值时,setter 会被触发,更新数据并通知所有依赖进行更新。
function set(newVal) {
if (newVal !== this.value) {
this.value = newVal;
this.dep.notify(); // 通知所有依赖
}
}
总结
- 对象劫持:通过 Object.defineProperty 劫持对象的属性,实现对属性的读写操作的拦截。
- 递归劫持:递归地将嵌套对象转化为响应式对象。
- 依赖管理:Dep 类用于管理和通知依赖。
- 计算属性与侦听器:缓存计算属性,利用侦听器响应数据变化。
- 模板编译:将模板编译为渲染函数,自动更新视图。
这些细节使 Vue 2 能够高效地实现双向数据绑定和响应式更新,确保视图和数据的一致性。