结合vue源码,分析vue2及vue3的数据绑定实现原理
- Vue 2 数据绑定实现
- 整体思路
- 详细实现
- 1. `Observer` 类:数据劫持
- 2. `Dep` 类:依赖收集
- 3. `Watcher` 类:订阅者
- Vue 3 数据绑定实现
- 整体思路
- 详细实现
- 1. `reactive` 函数:创建响应式对象
- 2. 依赖收集和触发更新
- 3. `effect` 函数:副作用收集
- 总结
Vue 2 数据绑定实现
整体思路
Vue 2 的数据绑定主要基于 Object.defineProperty()
方法来实现数据劫持,配合发布 - 订阅模式,当数据发生变化时通知视图更新。主要涉及三个核心部分:Observer
用于数据劫持、Dep
进行依赖收集、Watcher
作为订阅者接收更新通知。
详细实现
1. Observer
类:数据劫持
// 定义 Observer 类,用于对对象属性进行劫持
class Observer {
constructor(data) {
// 遍历对象属性
this.walk(data);
}
walk(data) {
if (!data || typeof data!== 'object') {
return;
}
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key]);
});
}
defineReactive(obj, key, val) {
// 创建一个 Dep 实例用于依赖收集
const dep = new Dep();
// 递归处理子对象
new Observer(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
if (Dep.target) {
dep.depend();
}
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// 对新值进行递归处理
new Observer(newVal);
// 通知依赖更新
dep.notify();
}
});
}
}
Observer
类会遍历对象的所有属性,使用 Object.defineProperty()
重写属性的 getter
和 setter
。在 getter
中收集依赖,在 setter
中通知依赖更新。
2. Dep
类:依赖收集
// Dep 类用于管理依赖
class Dep {
constructor() {
// 存储依赖的数组
this.subs = [];
}
depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
Dep.target = null;
Dep
类有一个 subs
数组来存储依赖(Watcher
实例)。depend
方法用于收集依赖,notify
方法用于通知所有依赖更新。
3. Watcher
类:订阅者
// Watcher 类作为订阅者,监听数据变化
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
this.cb = cb;
this.getter = parsePath(expOrFn);
this.value = this.get();
}
get() {
Dep.target = this;
const value = this.getter.call(this.vm, this.vm);
Dep.target = null;
return value;
}
addDep(dep) {
dep.addSub(this);
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb.call(this.vm, this.value, oldValue);
}
}
function parsePath(path) {
const segments = path.split('.');
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return;
obj = obj[segments[i]];
}
return obj;
};
}
Watcher
类在实例化时会触发 get
方法,从而触发 getter
进行依赖收集。当数据变化时,Dep
会调用 Watcher
的 update
方法,update
方法会重新获取数据并调用回调函数更新视图。
Vue 3 数据绑定实现
整体思路
Vue 3 使用 ES6 的 Proxy
对象来实现数据劫持,结合 WeakMap
进行依赖收集,相比 Vue 2 更加灵活高效。核心部分包括 reactive
函数创建响应式对象、effect
函数用于副作用收集、track
和 trigger
函数进行依赖收集和触发更新。
详细实现
1. reactive
函数:创建响应式对象
// reactive 函数用于创建响应式对象
function reactive(target) {
const handler = {
get(target, key, receiver) {
// 收集依赖
track(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue!== value) {
// 触发更新
trigger(target, key);
}
return result;
}
};
return new Proxy(target, handler);
}
reactive
函数使用 Proxy
对象拦截对象的 get
和 set
操作。在 get
操作中调用 track
进行依赖收集,在 set
操作中调用 trigger
触发更新。
2. 依赖收集和触发更新
// 用于存储对象及其依赖映射的 WeakMap
const targetMap = new WeakMap();
function track(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
if (activeEffect) {
dep.add(activeEffect);
}
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}
targetMap
是一个 WeakMap
,用于存储对象及其依赖映射。track
函数负责收集依赖,将 activeEffect
添加到对应的依赖集合中。trigger
函数负责触发更新,遍历依赖集合并执行其中的副作用函数。
3. effect
函数:副作用收集
let activeEffect = null;
function effect(fn) {
const _effect = function () {
activeEffect = _effect;
fn();
activeEffect = null;
};
_effect();
return _effect;
}
effect
函数用于创建副作用函数,在执行副作用函数之前将其赋值给 activeEffect
,以便在 track
函数中进行依赖收集。
总结
- Vue 2:使用
Object.defineProperty()
实现数据劫持,需要递归处理对象属性,对于新增和删除属性需要额外处理。 - Vue 3:使用
Proxy
对象实现数据劫持,能拦截更多操作,无需递归处理,对新增和删除属性的处理更加自然,性能和灵活性更高。