Vue3源码深度解析(一):响应式系统原理与实现
前言
2020年9月18日,Vue.js发布版3.0版本,代号:One Piece,Vue3作为Vue.js框架的重大版本升级,在性能、可维护性和开发体验上都有了质的飞跃。本系列文章将深入剖析Vue3的核心源码,帮助大家从底层理解Vue3的设计思想和实现原理。
本文是系列的第一篇,重点讲解Vue3响应式系统的核心原理。
一、Vue3响应式系统概述
1.1 Vue2 vs Vue3响应式对比
Vue2的响应式实现:
- 基于
Object.defineProperty实现 - 需要递归遍历所有属性进行劫持
- 无法监听对象属性的新增和删除
- 无法直接监听数组索引和length的变化
Vue3的响应式实现:
- 基于ES6的
Proxy实现 - 可以监听对象属性的新增和删除
- 可以直接监听数组的变化
- 性能更优,不需要递归遍历所有属性
- 支持Map、Set、WeakMap、WeakSet等数据结构
1.2 响应式系统的核心概念
在深入源码前,我们需要理解几个核心概念:
- Proxy:代理对象,用于拦截对象的读写操作
- Reflect:配合Proxy使用,提供对象操作的默认行为
- Track(依赖收集):在getter中收集依赖
- Trigger(触发更新):在setter中触发依赖更新
- Effect:副作用函数,响应式数据变化时需要执行的函数
- ReactiveEffect:副作用类,用于管理依赖关系
二、Proxy与Reflect基础
2.1 Proxy基本用法
在分析Vue3源码前,先来回顾Proxy的基本用法:
const obj = { name: 'Vue3', version: 3 };
const proxy = new Proxy(obj, {
// 拦截读取操作
get(target, key, receiver) {
console.log(`读取了${key}属性`);
return Reflect.get(target, key, receiver);
},
// 拦截设置操作
set(target, key, value, receiver) {
console.log(`设置${key}属性为${value}`);
return Reflect.set(target, key, value, receiver);
},
// 拦截属性删除
deleteProperty(target, key) {
console.log(`删除了${key}属性`);
return Reflect.deleteProperty(target, key);
},
// 拦截in操作符
has(target, key) {
console.log(`检查${key}属性是否存在`);
return Reflect.has(target, key);
}
});
proxy.name; // 输出:读取了name属性
proxy.count = 10; // 输出:设置count属性为10
2.2 为什么使用Reflect
Reflect的作用主要有三点:
- 保证正确的this指向:通过receiver参数确保this指向代理对象
- 统一操作接口:提供统一的对象操作方法
- 返回操作结果:返回布尔值表示操作是否成功
const obj = {
_value: 1,
get value() {
return this._value;
}
};
const proxy = new Proxy(obj, {
get(target, key, receiver) {
// 使用Reflect确保this指向proxy而不是obj
return Reflect.get(target, key, receiver);
}
});
三、响应式核心:reactive源码解析
3.1 reactive的基本实现
Vue3中的reactive函数用于将对象转换为响应式对象。让我们看看简化版的实现:
// 存储原始对象与代理对象的映射关系
const reactiveMap = new WeakMap();
function reactive(target) {
// 如果不是对象,直接返回
if (typeof target !== 'object' || target === null) {
return target;
}
// 如果已经是响应式对象,直接返回
if (reactiveMap.has(target)) {
return reactiveMap.get(target);
}
// 创建代理对象
const proxy = new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
const result = Reflect.get(target, key, receiver);
// 如果属性值是对象,递归转换为响应式
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
},
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;
},
deleteProperty(target, key) {
const hadKey = Object.prototype.hasOwnProperty.call(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey && result) {
trigger(target, key);
}
return result;
}
});
// 缓存代理对象
reactiveMap.set(target, proxy);
return proxy;
}
3.2 依赖收集:track函数
依赖收集是响应式系统的核心之一。Vue3使用了一个三层的数据结构来存储依赖关系:
// 存储依赖关系的数据结构
// WeakMap: target -> Map
// Map: key -> Set
// Set: effects
const targetMap = new WeakMap();
// 当前正在执行的副作用函数
let activeEffect = null;
function track(target, key) {
// 如果没有activeEffect,说明不需要收集依赖
if (!activeEffect) {
return;
}
// 获取target对应的依赖Map
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// 获取key对应的依赖Set
let deps = depsMap.get(key);
if (!deps) {
depsMap.set(key, (deps = new Set()));
}
// 将当前副作用函数添加到依赖集合中
deps.add(activeEffect);
// 双向存储:副作用函数也要记录它被哪些属性依赖
activeEffect.deps.push(deps);
}
依赖收集的数据结构示例:
targetMap = {
target1: {
key1: [effect1, effect2],
key2: [effect1]
},
target2: {
key3: [effect2]
}
}
3.3 触发更新:trigger函数
当响应式数据发生变化时,需要触发所有相关的副作用函数:
function trigger(target, key) {
// 获取target对应的依赖Map
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
// 获取key对应的所有副作用函数
const effects = depsMap.get(key);
if (!effects) {
return;
}
// 创建新的Set来避免无限循环
// 因为执行effect可能会再次触发依赖收集
const effectsToRun = new Set(effects);
effectsToRun.forEach(effect => {
// 如果effect有调度器,使用调度器执行
if (effect.scheduler) {
effect.scheduler();
} else {
effect();
}
});
}
3.4 副作用函数:effect实现
effect函数用于创建一个响应式的副作用函数:
function effect(fn, options = {}) {
const _effect = function() {
// 清除之前的依赖
cleanup(_effect);
// 设置当前活跃的effect
activeEffect = _effect;
// 执行副作用函数,期间会触发依赖收集
const result = fn();
// 重置activeEffect
activeEffect = null;
return result;
};
// 用于存储该effect依赖的所有属性
_effect.deps = [];
// 存储调度器
_effect.scheduler = options.scheduler;
// 如果不是lazy,立即执行
if (!options.lazy) {
_effect();
}
return _effect;
}
function cleanup(effect) {
// 从所有依赖集合中移除该effect
const { deps } = effect;
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect);
}
deps.length = 0;
}
}
3.5 完整示例
让我们通过一个完整的示例来理解响应式系统的工作流程:
// 创建响应式对象
const state = reactive({
count: 0,
message: 'Hello'
});
// 创建副作用函数
effect(() => {
console.log('count的值是:', state.count);
console.log('message的值是:', state.message);
});
// 输出:count的值是:0
// 输出:message的值是:Hello
// 修改数据,自动触发副作用函数
state.count++;
// 输出:count的值是:1
// 输出:message的值是:Hello
state.message = 'World';
// 输出:count的值是:1
// 输出:message的值是:World
执行流程分析:
reactive(obj)创建代理对象effect(fn)执行时,设置activeEffect = fn- 访问
state.count触发get,调用track(target, 'count')收集依赖 - 访问
state.message触发get,调用track(target, 'message')收集依赖 - 修改
state.count触发set,调用trigger(target, 'count')执行副作用函数 - 副作用函数重新执行,重新收集依赖
四、响应式进阶:处理边界情况
4.1 防止重复代理
// 已经是响应式对象的标记
const ReactiveFlags = {
IS_REACTIVE: '__v_isReactive'
};
function reactive(target) {
// 如果已经是响应式对象,直接返回
if (target && target[ReactiveFlags.IS_REACTIVE]) {
return target;
}
// ... 创建代理对象的代码
// 在代理对象的get拦截中返回标记
const proxy = new Proxy(target, {
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true;
}
// ... 其他get逻辑
}
});
return proxy;
}
4.2 处理数组
对于数组,Vue3做了特殊处理来优化性能:
const arrayInstrumentations = {};
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
arrayInstrumentations[method] = function(...args) {
// 暂停依赖收集
pauseTracking();
// 执行原始方法
const res = Array.prototype[method].apply(this, args);
// 恢复依赖收集
resetTracking();
return res;
};
});
// 在get拦截中使用
get(target, key, receiver) {
if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
// ... 其他逻辑
}
4.3 处理集合类型
对于Map、Set等集合类型,Vue3提供了专门的处理逻辑:
const mutableInstrumentations = {
get(key) {
const target = this.__v_raw;
track(target, key);
return target.get(key);
},
set(key, value) {
const target = this.__v_raw;
const hadKey = target.has(key);
const result = target.set(key, value);
if (!hadKey) {
trigger(target, key, 'add');
} else {
trigger(target, key, 'set');
}
return result;
}
};
五、性能优化技巧
5.1 使用WeakMap的优势
Vue3使用WeakMap存储依赖关系,有以下优势:
- 自动垃圾回收:当target对象没有被引用时,会自动从WeakMap中移除
- 避免内存泄漏:不会阻止target对象被垃圾回收
- 性能更好:不需要手动清理依赖关系
5.2 懒代理(Lazy Proxy)
Vue3对嵌套对象采用懒代理策略:
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
// 只有在访问时才将嵌套对象转换为响应式
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
}
这样做的好处是:
- 减少初始化开销
- 只代理实际访问的对象
- 提高大型对象的性能
六、总结
本文深入剖析了Vue3响应式系统的核心原理:
- Proxy基础:理解Proxy和Reflect的工作机制
- reactive实现:如何创建响应式对象
- 依赖收集:track函数的三层数据结构设计
- 触发更新:trigger函数如何执行副作用函数
- effect函数:副作用函数的创建和执行机制
- 边界处理:数组、集合类型的特殊处理
- 性能优化:WeakMap和懒代理的使用
掌握这些核心原理后,就能深刻理解Vue3响应式系统的设计思想。在下一篇文章中,将继续探讨ref、computed等派生响应式API的实现原理。
相关资源
- Vue3官方文档:https://cn.vuejs.org/
- Vue3 GitHub仓库:https://github.com/vuejs/core
- MDN Proxy文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
264

被折叠的 条评论
为什么被折叠?



