Vue.js 响应式系统源码解析
Vue 2 响应式系统
1. 响应式数据的初始化
当创建一个 Vue 实例时,Vue 会遍历 data
对象的所有属性,并将它们转换为响应式属性。以下是关键的源码实现:
function defineReactive(obj, key, val) {
const dep = new Dep(); // 用于收集依赖和通知变化
let childOb = observe(val); // 递归观察子对象
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend(); // 如果值是对象,也要收集依赖
}
}
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
childOb = observe(newVal); // 更新新值的观察
dep.notify(); // 通知视图更新
}
});
}
defineReactive 函数将对象的属性 key 转换为响应式属性。
dep 是一个 Dep 实例,用于管理依赖和通知变化。
Object.defineProperty 用于定义属性的 getter 和 setter。
getter 中的 Dep.target 是当前正在计算依赖的 Watcher 实例。
setter 用于处理数据变化,并通知所有相关的 Watcher 更新视图。
2. 依赖收集 (Dep)
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
Dep 类负责管理所有依赖(即 Watcher 实例)。
addSub 将 Watcher 添加到依赖列表。
depend 方法在 getter 中调用,用于收集依赖。
notify 方法在数据变化时调用,用于通知所有依赖更新视图。
3. Watcher 的实现
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
this.cb = cb;
this.expOrFn = expOrFn;
this.deps = [];
this.depsId = new Set();
this.get();
}
get() {
Dep.target = this;
// 执行传入的函数来触发数据访问
this.expOrFn.call(this.vm, this.vm);
Dep.target = null;
}
addDep(dep) {
const id = dep.id;
if (!this.depsId.has(id)) {
this.deps.push(dep);
this.depsId.add(id);
dep.addSub(this);
}
}
update() {
// 当数据变化时调用
this.cb();
}
}
Watcher 负责处理数据变化时的回调。
get 方法会执行传入的函数,触发数据访问并收集依赖。
addDep 将 Dep 实例添加到 Watcher 的依赖列表中。
update 方法在数据变化时被调用,用于更新视图。
4. 观察对象 (observe)
function observe(value) {
if (!value || typeof value !== 'object') {
return;
}
if (value.__ob__) {
return value.__ob__;
}
return new Observer(value);
}
class Observer {
constructor(value) {
this.value = value;
this.dep = new Dep();
Object.defineProperty(value, '__ob__', {
enumerable: false,
configurable: false,
value: this
});
if (Array.isArray(value)) {
// 特殊处理数组
this.observeArray(value);
} else {
this.walk(value);
}
}
walk(obj) {
Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));
}
observeArray(items) {
items.forEach(item => observe(item));
}
}
observe 函数用于创建 Observer 实例。
Observer 类负责将对象的所有属性转化为响应式。
walk 方法遍历对象的属性并调用 defineReactive。
observeArray 方法专门处理数组的响应式数据。
Vue 3 响应式系统
1. 创建响应式对象
在 Vue 3 中,响应式系统通过 reactive 函数实现,底层使用 Proxy 进行数据的代理。
import { reactive, effect } from 'vue';
// 创建响应式对象
const state = reactive({
count: 0
});
reactive 是 Vue 3 中创建响应式对象的主要方法。
它使用 Proxy 来代理对象的访问和修改操作,实现响应式功能。
2. Proxy 的实现
Vue 3 使用 Proxy 的 get 和 set 捕获器来拦截对对象的操作,实现响应式。
function createReactiveObject(target, baseHandlers) {
return new Proxy(target, baseHandlers);
}
const baseHandlers = {
get(target, key, receiver) {
// 收集依赖
const result = Reflect.get(target, key, receiver);
track(target, key);
return result;
},
set(target, key, value, receiver) {
// 修改数据
const result = Reflect.set(target, key, value, receiver);
trigger(target, key);
return result;
}
};
createReactiveObject 函数创建一个新的 Proxy 实例。
baseHandlers 定义了 Proxy 的 get 和 set 捕获器。
get 捕获器用于拦截对对象属性的读取操作,收集依赖。
set 捕获器用于拦截对对象属性的修改操作,触发更新。
3. 收集依赖和触发更新
const targetMap = new Map();
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()));
}
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());
}
}
let activeEffect = null;
function effect(fn) {
const _effect = () => {
activeEffect = _effect;
fn();
activeEffect = null;
};
_effect();
}
track 函数用于收集依赖,它将当前活跃的 effect 函数添加到依赖集合中。
trigger 函数用于触发更新,它会通知所有依赖的 effect 函数执行。
activeEffect 记录当前活跃的 effect 函数,用于依赖收集。
effect 函数用于创建和注册副作用函数。
4. 深度响应式(嵌套对象和数组)
const baseHandlers = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
track(target, key);
return result;
}
};
get 捕获器中,如果读取的值是对象类型,递归调用 reactive 使其也变为响应式。
5. ref 和 shallowReactive
import { ref, shallowReactive } from 'vue';
// ref 用于创建基本数据类型的响应式引用
const count = ref(0);
// shallowReactive 用于创建浅响应式对象
const state = shallowReactive({
count: 0
});
ref 用于创建基本数据类型的响应式引用,它会返回一个包含 value 属性的对象。
shallowReactive 用于创建浅响应式对象,它只会使对象的第一层属性变为响应式。
总结
Vue 3 的响应式系统相比 Vue 2 更加高效和灵活,主要通过 Proxy 实现,能够更好地处理深度嵌套的对象和数组。它通过 track 和 trigger 函数来实现依赖收集和视图更新,具有更好的性能和更简洁的 API。