Vue 响应式原理深度解析
⚠️面试要点
常见面试题
Q1: Vue 2 的响应式原理是什么?
// 回答要点:
1. 使用 Object.defineProperty 劫持对象的 getter/setter
2. 每个属性都有对应的 Dep 依赖收集器
3. 在 getter 中收集依赖(Watcher)
4. 在 setter 中通知依赖更新
5. 数组需要特殊处理(重写7个方法)
Q2: Vue 3 为什么要用 Proxy 替代 Object.defineProperty?
// 回答要点:
1. Proxy 可以直接代理整个对象,不用遍历属性
2. 支持数组索引变化、length 变化的检测
3. 支持动态新增/删除属性
4. 性能更好,按需响应
5. 更好的浏览器兼容性
Q3: Vue 的异步更新机制是如何工作的?
// 回答要点:
1. 数据变化时,Watcher 不会立即执行,而是加入队列
2. 同一个 Watcher 在同一个事件循环中只会被添加一次
3. 通过 nextTick 在下一个事件循环执行更新
4. 使用微任务优先(Promise > MutationObserver > setImmediate > setTimeout)
5. 保证更新顺序(父组件在子组件之前)
1. 核心思想
响应式原理的本质:数据变化 → 自动更新视图
// 传统方式
data.name = '张三';
document.getElementById('name').innerText = data.name; // 手动更新
// Vue 响应式
vm.name = '张三'; // 自动更新所有使用到 name 的地方
2. Vue 2 响应式原理(Object.defineProperty)
2.1 基本实现
// 简化的响应式实现
function defineReactive(obj, key, val) {
// 每个属性都有自己的依赖管理器
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log(`读取 ${key}: ${val}`);
// 依赖收集
if (Dep.target) {
dep.depend(); // 将当前 watcher 添加到依赖列表
}
return val;
},
set(newVal) {
console.log(`修改 ${key}: ${val} -> ${newVal}`);
if (newVal === val) return;
val = newVal;
// 派发更新
dep.notify(); // 通知所有依赖的 watcher 更新
}
});
}
2.2 依赖收集系统
// 依赖管理器(发布者)
let uid = 0;
class Dep {
static target = null; // 当前正在计算的 watcher
constructor() {
this.id = uid++;
this.subs = []; // 订阅者列表
}
// 添加订阅者
addSub(sub) {
this.subs.push(sub);
}
// 收集依赖
depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
}
// 通知所有订阅者更新
notify() {
const subs = this.subs.slice();
for (let i = 0; i < subs.length; i++) {
subs[i].update(); // 触发每个 watcher 更新
}
}
}
// 观察者(订阅者)
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
this.cb = cb;
this.deps = [];
this.depIds = new Set();
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = this.parseGetter(expOrFn);
}
this.value = this.get(); // 首次求值,触发依赖收集
}
get() {
Dep.target = this; // 设置当前 watcher
let value;
try {
value = this.getter.call(this.vm, this.vm);
} finally {
Dep.target = null; // 收集完成
}
return value;
}
addDep(dep) {
const id = dep.id;
if (!this.depIds.has(id)) {
this.depIds.add(id);
this.deps.push(dep);
dep.addSub(this);
}
}
update() {
// 异步更新队列
queueWatcher(this);
}
run() {
const value = this.get();
const oldValue = this.value;
if (value !== oldValue) {
this.value = value;
this.cb.call(this.vm, value, oldValue);
}
}
}
2.3 Observer 观察者类
class Observer {
constructor(value) {
this.value = value;
this.dep = new Dep();
// 标记对象已被观察
def(value, '__ob__', this);
if (Array.isArray(value)) {
// 数组的特殊处理
this.observeArray(value);
} else {
this.walk(value);
}
}
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]]);
}
}
observeArray(items) {
for (let i = 0; i < items.length; i++) {
observe(items[i]);
}
}
}
// 对外暴露的 observe 函数
function observe(value) {
if (!isObject(value) || value instanceof VNode) {
return;
}
let ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
return ob;
}
3. 数组的响应式处理
3.1 数组方法重写
// 需要拦截的数组方法
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
methodsToPatch.forEach(function(method) {
const original = arrayProto[method];
def(arrayMethods, method, function mutator(...args) {
console.log(`数组方法 ${method} 被调用`);
const result = original.apply(this, args);
const ob = this.__ob__;
let inserted;
// 对新插入的元素进行响应式处理
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2);
break;
}
if (inserted) ob.observeArray(inserted);
// 通知更新
ob.dep.notify();
return result;
});
});
3.2 数组响应式的局限性
// Vue 2 中数组响应式的限制
const vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
});
// ✅ 响应式的方法
vm.items.push('d'); // 正确
vm.items.splice(0, 1, 'x'); // 正确
vm.items.sort(); // 正确
// ❌ 非响应式的方法
vm.items[0] = 'x'; // 不会触发更新
vm.items.length = 0; // 不会触发更新
// ✅ 解决方法
Vue.set(vm.items, 0, 'x'); // 正确
vm.items.splice(0, 1, 'x'); // 正确
4. Vue 3 响应式原理(Proxy)
4.1 Proxy 基本实现
// Vue 3 的 reactive 函数
function reactive(target) {
return createReactiveObject(
target,
mutableHandlers, // 普通对象的处理器
mutableCollectionHandlers, // 集合类型的处理器
reactiveMap // 缓存映射
);
}
function createReactiveObject(target, baseHandlers, collectionHandlers, proxyMap) {
// 如果不是对象,直接返回
if (!isObject(target)) {
return target;
}
// 如果已经是响应式对象,直接返回
if (target[ReactiveFlags.RAW]) {
return target;
}
// 如果已经存在代理,返回缓存
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// 创建代理
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
);
// 缓存代理
proxyMap.set(target, proxy);
return proxy;
}
4.2 Proxy 处理器
// 普通对象的处理器
const mutableHandlers = {
get(target, key, receiver) {
// 追踪依赖
track(target, key);
const res = Reflect.get(target, key, receiver);
// 深层响应式
if (isObject(res)) {
return reactive(res);
}
return res;
},
set(target, key, value, receiver) {
const oldValue = target[key];
// 判断是新增属性还是修改属性
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
// 新增属性
trigger(target, key, TriggerOpTypes.ADD, value);
} else if (hasChanged(value, oldValue)) {
// 修改属性
trigger(target, key, TriggerOpTypes.SET, value, oldValue);
}
return result;
},
deleteProperty(target, key) {
const hadKey = hasOwn(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey && result) {
trigger(target, key, TriggerOpTypes.DELETE);
}
return result;
}
};
4.3 依赖追踪系统
// 依赖追踪
const targetMap = new WeakMap(); // 存储所有响应式对象的依赖
let activeEffect = null; // 当前活动的 effect
function track(target, key) {
if (!activeEffect) return;
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);
activeEffect.deps.push(dep);
}
function trigger(target, key, type, newValue, oldValue) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const effects = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
effects.add(effect);
});
}
};
// 调度执行
const run = (effect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect);
} else {
effect();
}
};
effects.forEach(run);
}
5. 响应式系统的核心组件
5.1 响应式数据创建
// Vue 2
new Vue({
data() {
return {
message: 'Hello',
user: { name: 'John' },
list: [1, 2, 3]
};
}
});
// Vue 3 Composition API
import { reactive, ref } from 'vue';
export default {
setup() {
const state = reactive({
message: 'Hello',
user: { name: 'John' }
});
const count = ref(0);
return { state, count };
}
};
5.2 计算属性实现原理
// 计算属性本质是特殊的 Watcher/Effect
class ComputedRefImpl {
constructor(getter, setter) {
this._dirty = true; // 脏检查标记
this._value = undefined;
this._getter = getter;
this._setter = setter;
// 创建 effect
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true;
triggerRefValue(this);
}
});
}
get value() {
// 收集依赖
trackRefValue(this);
if (this._dirty) {
this._dirty = false;
this._value = this.effect.run();
}
return this._value;
}
set value(newValue) {
this._setter(newValue);
}
}
5.3 侦听器实现原理
// watch 实现原理
function watch(source, cb, options) {
return doWatch(source, cb, options);
}
function doWatch(source, cb, options) {
let getter;
let deep = false;
if (typeof source === 'function') {
getter = source;
} else {
getter = () => traverse(source);
deep = true;
}
let oldValue = {};
const job = () => {
if (cb) {
const newValue = effect.run();
if (deep || hasChanged(newValue, oldValue)) {
cb(newValue, oldValue);
oldValue = newValue;
}
}
};
const effect = new ReactiveEffect(getter, job);
// 初始执行
oldValue = effect.run();
if (options.immediate) {
job();
}
return () => {
effect.stop();
};
}
6. 异步更新队列
6.1 为什么需要异步更新
// 同步更新的问题
vm.count = 1;
vm.count = 2;
vm.count = 3;
// 会触发三次重新渲染,性能差
// Vue 的解决方案:异步更新队列
let queue = [];
let waiting = false;
function queueWatcher(watcher) {
const id = watcher.id;
if (!has[id]) {
has[id] = true;
queue.push(watcher);
if (!waiting) {
waiting = true;
nextTick(flushSchedulerQueue);
}
}
}
function flushSchedulerQueue() {
queue.sort((a, b) => a.id - b.id);
for (let i = 0; i < queue.length; i++) {
const watcher = queue[i];
has[watcher.id] = null;
watcher.run();
}
// 重置队列
resetSchedulerState();
}
6.2 nextTick 实现
const callbacks = [];
let pending = false;
function nextTick(cb, ctx) {
let _resolve;
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc(); // 异步执行
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve;
});
}
}
// 异步执行器
let timerFunc;
if (typeof Promise !== 'undefined') {
// 优先使用 Promise
const p = Promise.resolve();
timerFunc = () => {
p.then(flushCallbacks);
};
} else if (typeof MutationObserver !== 'undefined') {
// 降级到 MutationObserver
let counter = 1;
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, { characterData: true });
timerFunc = () => {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
} else if (typeof setImmediate !== 'undefined') {
// 再降级到 setImmediate
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// 最后使用 setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
7. 虚拟DOM与响应式结合
7.1 渲染Watcher
// 渲染 Watcher
new Watcher(vm, updateComponent, noop, {
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
function updateComponent() {
vm._update(vm._render(), hydrating);
}
7.2 Patch 过程
// 虚拟DOM对比更新
function patch(oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode);
return;
}
let isInitialPatch = false;
const insertedVnodeQueue = [];
if (isUndef(oldVnode)) {
// 新增节点
isInitialPatch = true;
createElm(vnode, insertedVnodeQueue);
} else {
// 更新节点
const isRealElement = isDef(oldVnode.nodeType);
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// 相同节点,进行 patch
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
} else {
// 不同节点,替换
createElm(vnode, insertedVnodeQueue, oldVnode.elm);
}
}
return vnode.elm;
}
8. Vue 2 与 Vue 3 响应式对比
8.1 技术实现对比
| 特性 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
|---|---|---|
| 数据劫持方式 | 属性劫持 | 代理整个对象 |
| 数组响应式 | 需要重写方法 | 原生支持 |
| 新增属性检测 | 不支持 | 支持 |
| 删除属性检测 | 不支持 | 支持 |
| 性能 | 递归遍历所有属性 | 惰性代理 |
| 内存占用 | 每个属性一个 Dep | 按需收集 |
8.2 使用差异
// Vue 2
export default {
data() {
return {
user: { name: 'John' },
list: [1, 2, 3]
};
},
created() {
// ❌ 不会响应
this.user.age = 30;
this.list[0] = 100;
// ✅ 需要特殊处理
this.$set(this.user, 'age', 30);
this.list.splice(0, 1, 100);
}
};
// Vue 3
import { reactive } from 'vue';
export default {
setup() {
const state = reactive({
user: { name: 'John' },
list: [1, 2, 3]
});
// ✅ 直接响应
state.user.age = 30; // 自动响应
state.list[0] = 100; // 自动响应
delete state.user.name; // 自动响应
return { state };
}
};
9. 响应式系统的限制和注意事项
9.1 无法检测的变化
// Vue 2 中的限制
const vm = new Vue({
data: {
obj: { a: 1 },
arr: [1, 2, 3]
}
});
// 1. 新增对象属性
vm.obj.b = 2; // ❌ 不会响应
Vue.set(vm.obj, 'b', 2); // ✅ 正确
// 2. 删除对象属性
delete vm.obj.a; // ❌ 不会响应
Vue.delete(vm.obj, 'a'); // ✅ 正确
// 3. 数组索引设置
vm.arr[0] = 100; // ❌ 不会响应
vm.arr.splice(0, 1, 100); // ✅ 正确
Vue.set(vm.arr, 0, 100); // ✅ 正确
// 4. 修改数组长度
vm.arr.length = 0; // ❌ 不会响应
vm.arr.splice(0); // ✅ 正确
9.2 性能优化
// 1. 避免大数据结构的响应式
data() {
return {
// ❌ 大型列表完全响应式
largeList: Array(10000).fill().map((_, i) => ({ id: i })),
// ✅ 只对需要的属性响应式
paginatedList: [], // 分页加载
};
},
// 2. 冻结不需要响应的数据
data() {
return {
config: Object.freeze({ // 不会被响应式处理
apiUrl: '/api',
timeout: 5000
})
};
},
// 3. 合理使用计算属性缓存
computed: {
// ✅ 自动缓存
filteredItems() {
return this.items.filter(item => item.active);
}
},
10. 完整示例代码
// 简化的 Vue 响应式系统实现
class SimpleVue {
constructor(options) {
this.$options = options;
this.$data = options.data();
// 响应式化
this.observe(this.$data);
// 编译
new Compile(options.el, this);
// 执行 created 钩子
options.created && options.created.call(this);
}
observe(data) {
if (!data || typeof data !== 'object') return;
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key]);
this.proxyData(key);
});
}
defineReactive(obj, key, val) {
const dep = new Dep();
this.observe(val);
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.depend();
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
this.observe(newVal);
dep.notify();
}
});
}
proxyData(key) {
Object.defineProperty(this, key, {
get() { return this.$data[key]; },
set(val) { this.$data[key] = val; }
});
}
}
// 使用示例
const app = new SimpleVue({
el: '#app',
data() {
return {
message: 'Hello Vue',
count: 0
};
},
created() {
console.log('实例创建完成');
}
});
// 自动更新
app.message = 'Changed'; // 触发更新
11. 总结
Vue 响应式系统的核心要点:
-
数据劫持:
- Vue 2: Object.defineProperty
- Vue 3: Proxy
-
依赖收集:
- 每个响应式数据都有对应的依赖收集器
- 在 getter 中收集当前正在计算的 Watcher
-
派发更新:
- 数据变化时通知所有依赖的 Watcher
- 通过异步更新队列优化性能
-
虚拟DOM:
- Watcher 触发重新渲染
- 生成新的虚拟DOM
- Diff 算法对比更新真实DOM
设计思想:
- 数据驱动视图
- 组件级更新
- 异步批量更新
- 按需响应(Vue 3)
实际应用理解:
// 当你在 Vue 中这样写时:
this.message = 'new value';
// Vue 内部发生的事情:
1. 触发 message 的 setter
2. 通知相关的 Dep
3. Dep 通知所有 Watcher
4. Watcher 加入更新队列
5. nextTick 后执行更新
6. 重新渲染组件
7. 虚拟DOM diff
8. 更新真实DOM
这就是 Vue 响应式系统的完整工作原理!