经过前面几篇文章的深入探索,我们从最底层的 effect 开始,逐步构建起了 Vue3 响应式系统的完整图景。今天,我们将把所有组件串联起来,形成一个完整的闭环,并通过实战和性能分析,深入理解 Vue3 响应式系统的设计精髓。
前言:响应式系统的全景图
在开始整合之前,让我们先回顾一下整个响应式系统的架构:
Vue3 中的响应式系统的核心思想可以概括为:在读取时收集依赖,在修改时触发更新。
串联所有组件:完整的响应式系统
1. 基础工具函数
javascript
// ============ 工具函数 ============
function isObject(value) {
return value !== null && typeof value === 'object';
}
function isFunction(value) {
return typeof value === 'function';
}
function isArray(value) {
return Array.isArray(value);
}
function isRef(r) {
return !!(r && r.__v_isRef === true);
}
function isReactive(value) {
return !!(value && value.__v_isReactive === true);
}
function isArrayIndex(key) {
if (typeof key !== 'string') return false;
const keyAsNumber = Number(key);
return Number.isInteger(keyAsNumber) &&
keyAsNumber >= 0 &&
keyAsNumber < Number.MAX_SAFE_INTEGER;
}
2. 依赖管理核心
javascript
// ============ 依赖管理 ============
const targetMap = new WeakMap();
let activeEffect = null;
// 操作类型枚举
const TrackOpTypes = {
GET: 'get',
HAS: 'has',
ITERATE: 'iterate'
};
const TriggerOpTypes = {
SET: 'set',
ADD: 'add',
DELETE: 'delete',
CLEAR: 'clear'
};
// 特殊标识
const ITERATE_KEY = Symbol('iterate');
class ReactiveEffect {
constructor(fn, scheduler = null) {
this.fn = fn;
this.scheduler = scheduler;
this.deps = [];
this.active = true;
this.runDepth = 0;
}
run() {
if (!this.active) {
return this.fn();
}
try {
this.runDepth++;
if (this.runDepth > 1000) {
console.warn('检测到可能的无限循环');
return;
}
activeEffect = this;
return this.fn();
} finally {
this.runDepth--;
activeEffect = null;
}
}
stop() {
if (this.active) {
this.active = false;
this.deps.forEach(dep => dep.delete(this));
this.deps.length = 0;
}
}
}
function track(target, type, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// 处理迭代操作
let depKey = key;
if (type === TrackOpTypes.ITERATE) {
depKey = ITERATE_KEY;
}
let dep = depsMap.get(depKey);
if (!dep) {
depsMap.set(depKey, (dep = new Set()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
function trigger(target, type, key, newValue, oldValue) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const effectsToRun = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect) {
effectsToRun.add(effect);
}
});
}
};
// 处理普通 key
if (key !== undefined) {
add(depsMap.get(key));
}
// 处理数组特殊情况
if (Array.isArray(target)) {
if (key === 'length') {
// length 变化需要触发所有索引 >= 新值的依赖
const newLength = Number(newValue);
depsMap.forEach((dep, key) => {
if (isArrayIndex(key) && Number(key) >= newLength) {
add(dep);
}
});
} else if (type === TriggerOpTypes.ADD && isArrayIndex(key)) {
// 添加数组元素触发 length
add(depsMap.get('length'));
}
} else {
// 处理迭代操作
if (type === TriggerOpTypes.ADD || type === TriggerOpTypes.DELETE) {
add(depsMap.get(ITERATE_KEY));
}
}
// 执行 effects
effectsToRun.forEach(effect => {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
});
}
function effect(fn) {
const _effect = new ReactiveEffect(fn);
_effect.run();
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
3. Reactive 实现
javascript
// ============ reactive ============
const reactiveHandlers = {
get(target, key, receiver) {
// 内部标记
if (key === '__v_isReactive') return true;
const value = Reflect.get(target, key, receiver);
// 依赖收集
track(target, TrackOpTypes.GET, key);
// 嵌套响应式
if (isObject(value)) {
return reactive(value);
}
return value;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const hadKey = target.hasOwnProperty(key);
const oldLength = Array.isArray(target) ? target.length : undefined;
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
// 新增属性
trigger(target, TriggerOpTypes.ADD, key, value);
} else if (oldValue !== value) {
// 修改属性
trigger(target, TriggerOpTypes.SET, key, value, oldValue);
}
// 数组 length 隐式变化
if (Array.isArray(target) && oldLength !== target.length) {
trigger(target, TriggerOpTypes.SET, 'length', target.length);
}
return result;
},
deleteProperty(target, key) {
const hadKey = target.hasOwnProperty(key);
const oldValue = target[key];
const result = Reflect.deleteProperty(target, key);
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue);
}
return result;
},
has(target, key) {
track(target, TrackOpTypes.HAS, key);
return Reflect.has(target, key);
},
ownKeys(target) {
track(target, TrackOpTypes.ITERATE, ITERATE_KEY);
return Reflect.ownKeys(target);
}
};
function reactive(target) {
if (!isObject(target)) return target;
if (target.__v_isReactive) return target;
return new Proxy(target, reactiveHandlers);
}
4. Ref 实现
javascript
// ============ ref ============
class RefImpl {
constructor(value, isShallow = false) {
this._rawValue = value;
this._value = isShallow ? value : toReactive(value);
this.__v_isRef = true;
this._isShallow = isShallow;
}
get value() {
track(this, TrackOpTypes.GET, 'value');
return this._value;
}
set value(newValue) {
if (newValue !== this._rawValue) {
this._rawValue = newValue;
this._value = this._isShallow ? newValue : toReactive(newValue);
trigger(this, TriggerOpTypes.SET, 'value', newValue);
}
}
}
function toReactive(value) {
return isObject(value) ? reactive(value) : value;
}
function ref(value) {
if (isRef(value)) return value;
return new RefImpl(value);
}
function shallowRef(value) {
return new RefImpl(value, true);
}
function isRef(r) {
return !!(r && r.__v_isRef === true);
}
function unref(ref) {
return isRef(ref) ? ref.value : ref;
}
function toReactive(value) {
return isObject(value) ? reactive(value) : value;
}
class ObjectRefImpl {
constructor(_object, _key) {
this._object = _object;
this._key = _key;
this.__v_isRef = true;
}
get value() {
return this._object[this._key];
}
set value(newValue) {
this._object[this._key] = newValue;
}
}
function toRef(object, key) {
return new ObjectRefImpl(object, key);
}
function toRefs(object) {
const result = {};
for (const key in object) {
if (object.hasOwnProperty(key)) {
result[key] = toRef(object, key);
}
}
return result;
}
5. Computed 实现
javascript
// ============ computed ============
class ComputedRefImpl {
constructor(getter, setter) {
this.getter = getter;
this.setter = setter;
this._dirty = true;
this._value = undefined;
this.__v_isRef = true;
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true;
trigger(this, TriggerOpTypes.SET, 'value');
}
});
}
get value() {
track(this, TrackOpTypes.GET, 'value');
if (this._dirty) {
this._dirty = false;
this._value = this.effect.run();
}
return this._value;
}
set value(newValue) {
if (this.setter) {
this.setter(newValue);
} else {
console.warn('计算属性是只读的');
}
}
}
function computed(getterOrOptions) {
let getter, setter;
if (isFunction(getterOrOptions)) {
getter = getterOrOptions;
setter = null;
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
return new ComputedRefImpl(getter, setter);
}
6. Watch 实现
javascript
// ============ watch ============
function traverse(value, seen = new Set()) {
if (!isObject(value) || seen.has(value)) return value;
seen.add(value);
if (Array.isArray(value)) {
value.forEach(item => traverse(item, seen));
} else if (value instanceof Map) {
value.forEach((v, k) => {
traverse(v, seen);
traverse(k, seen);
});
} else if (value instanceof Set) {
value.forEach(v => traverse(v, seen));
} else {
Object.keys(value).forEach(key => traverse(value[key], seen));
}
return value;
}
function watch(source, cb, options = {}) {
let getter;
if (isRef(source)) {
getter = () => source.value;
} else if (isReactive(source)) {
getter = () => source;
options.deep = options.deep ?? true;
} else if (Array.isArray(source)) {
getter = () => source.map(s => {
if (isRef(s)) return s.value;
if (isReactive(s)) return traverse(s);
if (isFunction(s)) return s();
return s;
});
} else if (isFunction(source)) {
if (cb) {
getter = source;
} else {
return watchEffect(source, options);
}
}
if (options.deep) {
const baseGetter = getter;
getter = () => traverse(baseGetter());
}
let oldValue;
let cleanup;
function onInvalidate(fn) {
cleanup = fn;
}
const scheduler = () => {
if (cleanup) cleanup();
const newValue = getter();
if (newValue !== oldValue) {
cb(newValue, oldValue, onInvalidate);
}
oldValue = newValue;
};
const _effect = new ReactiveEffect(getter, scheduler);
if (options.immediate) {
scheduler();
} else {
oldValue = _effect.run();
}
return () => {
_effect.stop();
if (cleanup) cleanup();
};
}
function watchEffect(effect, options = {}) {
const scheduler = () => {
if (options.flush === 'post') {
Promise.resolve().then(() => _effect.run());
} else {
_effect.run();
}
};
const _effect = new ReactiveEffect(effect, scheduler);
_effect.run();
return () => {
_effect.stop();
};
}
常见面试题解析
面试题 1:Vue3 的响应式原理是什么?
核心原理:Proxy + 依赖收集:
- 通过 Proxy 代理对象的所有操作
- 在 get 中通过 track 收集依赖
- 在 set 中通过 trigger 触发更新
- 使用 WeakMap + Map + Set 三层结构存储依赖
- 通过 effect 管理系统中的副作用
text
┌─────────────────────────────────────────────────────────────┐
│ 1. Proxy代理对象 │
│ │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ 原始对象 │ │ Proxy代理 │ │
│ │ { │ 代理 │ get: track收集 │ │
│ │ count: 0, │◄───────┤ set: trigger触发 │ │
│ │ name: 'vue'│ │ deleteProperty │ │
│ │ } │ │ has... │ │
│ └──────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. 依赖收集 (track) │
│ │
│ get操作触发 ──→ track函数 ──→ 查找依赖存储 │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 3. 三层依赖存储结构 │ │
│ │ │ │
│ │ WeakMap Map Set │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │target│──────►│key1 │─────►│effect1│ │ │
│ │ └─────┘ ├─────┤ ├─────┤ │ │
│ │ │key2 │─┐ │effect2│ │ │
│ │ └─────┘ │ └─────┘ │ │
│ │ └───►┌─────┐ │ │
│ │ │effect3│ │ │
│ │ └─────┘ │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. 副作用管理 (effect) │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ effect(() => { │ │
│ │ console.log(obj.count) // 依赖收集 │ │
│ │ }) │ │
│ └────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ effect1 │ │ effect2 │ │ effect3 │ │
│ │ (更新UI) │ │(计算属性)│ │ (watch) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 5. 触发更新 (trigger) │
│ │
│ set操作触发 ──→ trigger函数 ──→ 从存储结构中查找依赖 │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 执行所有相关的副作用函数 │ │
│ │ │ │
│ │ obj.count = 1 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 触发更新 ──→ 执行effect1 ──→ 更新UI │ │
│ │ └─→ 执行effect2 ──→ 重新计算 │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
面试题 2:Vue2 的 Object.defineProperty 和 Vue3 的 Proxy 有什么区别?
Proxy可以监听新增/删除属性;defineProperty不能Proxy可以监听数组索引和 length;defineProperty不行Proxy需要递归代理;defineProperty初始化递归Proxy性能更好,拦截操作更丰富
面试题 3:为什么 ref 需要 .value 而 reactive 不需要?
因为 Proxy 无法代理原始值,对于原始值的代理需要通过 value 包裹成对象:
javascript
let count = 0; // 原始值,无法代理
// ref 包装成对象
const countRef = {
value: 0
};
// 现在可以对 countRef 进行代理
面试题 4:computed 和 watch 有什么区别?
| 对比维度 | computed | watch |
|---|---|---|
| 概念 | 计算属性,基于依赖缓存的计算值 | 侦听器,执行副作用操作 |
| 缓存机制 | 有缓存,只有依赖变化时才重新计算 | 无缓存,每次监听到变化都执行 |
| 返回值 | 必须返回一个值,模板中可直接使用 | 通常不返回值,用于执行逻辑操作 |
| 依赖追踪 | 自动追踪响应式依赖 | 手动指定要侦听的数据源 |
| 执行时机 | 懒执行,只有访问时才重新计算 | 立即执行(可配置)或数据变化时执行 |
| 异步操作 | 不支持异步 | 支持异步操作 |
| 性能特点 | 适合衍生状态,避免重复计算 | 适合处理开销大的操作或异步逻辑 |
| 使用场景 | 1. 模板中复杂表达式 2. 依赖其他数据的衍生值 3. 需要缓存的场景 | 1. 数据变化时执行异步操作 2. 操作DOM 3. 执行开销大的操作 |
| 访问方式 | 作为属性访问:state.count | 通过回调函数执行 |
| 深度监听 | 自动深度追踪依赖 | 需要手动配置 deep: true |
| 立即执行 | 自动计算 | 需要配置 immediate: true |
面试题 5:Vue3 的响应式系统如何避免循环依赖?
- activeEffect 守卫:
javascript
function trigger(target, key) {
const effects = depsMap.get(key);
effects.forEach(effect => {
// 跳过当前正在执行的 effect
if (effect !== activeEffect) {
effect.run();
}
});
}
- 使用 Set 避免重复收集
javascript
dep.add(activeEffect); // 自动去重
- 递归深度限制
javascript
class ReactiveEffect {
run() {
this.runDepth++;
if (this.runDepth > 1000) {
console.warn('检测到无限循环');
return;
}
// ... 执行逻辑
this.runDepth--;
}
}
性能分析:Vue3 响应式比 Vue2 快在哪里?
1. 初始化性能对比
javascript
// Vue2:递归遍历所有属性
function vue2Init(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key);
});
return obj;
}
// Vue3:代理整个对象,懒递归
function vue3Init(obj) {
return new Proxy(obj, handlers); // 不递归
// 只有在访问嵌套对象时才递归转换
}
性能差异:
- Vue2: O(n) 初始化时间,n 为所有属性数量
- Vue3: O(1) 初始化时间,只创建代理
2. 内存占用对比
javascript
// Vue2:为每个属性创建闭包
function defineReactive(obj, key) {
let value = obj[key];
const dep = new Dep(); // 每个属性一个 dep
Object.defineProperty(obj, key, {
get() {
dep.depend(); // 闭包引用
return value;
},
set(newVal) {
dep.notify();
}
});
}
// Vue3:共享 handlers,使用 WeakMap 存储依赖
const targetMap = new WeakMap(); // 依赖统一存储
const handlers = {}; // 单例,不重复创建
性能差异:
- Vue2: 每个属性都有独立的 getter/setter 和闭包
- Vue3: 所有对象共享 handlers,依赖集中存储
3. 数组操作性能
javascript
// Vue2:重写数组方法
const arrayMethods = ['push', 'pop', 'shift', 'unshift'];
arrayMethods.forEach(method => {
const original = Array.prototype[method];
Object.defineProperty(array, method, {
value: function(...args) {
const result = original.apply(this, args);
// 额外触发更新
return result;
}
});
});
// Vue3:Proxy 直接拦截
const arr = new Proxy([], {
set(target, key, value) {
target[key] = value;
// 统一处理更新
return true;
}
});
性能差异:
- Vue2: 需要拦截每个方法,有额外开销
- Vue3: 统一通过 set 拦截,更高效
4. 编译时优化
Vue3 性能提升:
- 静态节点只创建一次
- 更新时只比较动态部分
- 减少了不必要的 VNode 创建
5. 批量更新机制
javascript
// Vue2:同步更新
state.count++;
state.name = '张三'; // 触发两次更新
// Vue3:异步批量更新
state.count++;
state.name = '张三';
// 只触发一次更新
性能差异:
- Vue2: 多次同步更新导致多次渲染
- Vue3: 批量处理,减少渲染次数
结语
经过多篇文章的深入探索,我们完成了 Vue3 响应式系统的完整学习。响应式系统的设计思想,不仅适用于 Vue,也为我们理解和构建响应式应用提供了宝贵的参考。从底层原理到上层 API,每一层都是精心设计的结果,共同构成了这个优雅而强大的系统。
对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!