1524 字
8 分钟
Vue2 响应式系统
.png)
题目描述
实现 Vue2 的核心响应式系统,要求:
- 使用
Object.defineProperty
实现数据劫持 - 实现依赖收集(Dep)和依赖触发(Watcher)机制
- 支持嵌套对象的深层响应式处理
- 实现数组方法的劫持(push/pop/shift/unshift/splice/sort/reverse)
- 处理属性新增和删除的响应式更新
- 实现计算属性和侦听器基础功能
要求完整实现响应式逻辑,并通过测试用例验证。
解题思路
- 数据劫持:递归使用
Object.defineProperty
劫持对象属性 - 依赖收集:在 getter 中收集依赖(Dep -> Watcher)
- 依赖触发:在 setter 中通知变更(Watcher.update)
- 数组处理:拦截数组变异方法并手动触发更新
- 嵌套对象:递归处理对象属性实现深层响应
- 计算属性:基于 Watcher 实现惰性求值
关键洞察
- 响应式核心:通过 getter/setter 实现数据变化侦测
- 依赖追踪:Dep 收集 Watcher,Watcher 订阅 Dep
- 数组劫持:重写数组原型方法触发更新
- 深层监听:递归处理嵌套对象属性
- 惰性计算:计算属性缓存机制避免重复计算
- 批量更新:异步更新队列避免重复渲染
代码流程
flowchart TD A[Observer 监听数据] --> B{数据类型} B -->|对象| C[递归 defineProperty] B -->|数组| D[重写数组方法] C --> E[getter 收集依赖] E --> F[Dep 添加 Watcher] D --> G[手动触发更新] H[数据变更] --> I[setter 通知 Dep] I --> J[Watcher 更新] J --> K[执行视图更新]
代码实现
// 依赖收集器class Dep { constructor() { this.subs = new Set(); }
addSub(sub) { this.subs.add(sub); }
removeSub(sub) { this.subs.delete(sub); }
depend() { if (Dep.target) { Dep.target.addDep(this); } }
notify() { const subs = [...this.subs]; subs.forEach(sub => sub.update()); }}
Dep.target = null;const targetStack = [];
function pushTarget(target) { targetStack.push(target); Dep.target = target;}
function popTarget() { targetStack.pop(); Dep.target = targetStack[targetStack.length - 1];}
// 观察者class Watcher { constructor(vm, expOrFn, cb, options) { this.vm = vm; this.cb = cb; this.deps = new Set(); this.depIds = new Set(); this.getter = typeof expOrFn === 'function' ? expOrFn : () => vm[expOrFn];
if (options) { this.lazy = !!options.lazy; }
this.value = this.lazy ? undefined : this.get(); }
get() { pushTarget(this); const value = this.getter.call(this.vm); popTarget(); return value; }
addDep(dep) { if (!this.depIds.has(dep.id)) { this.depIds.add(dep.id); this.deps.add(dep); dep.addSub(this); } }
update() { if (this.lazy) { this.dirty = true; } else { queueWatcher(this); } }
run() { const value = this.get(); const oldValue = this.value; this.value = value; this.cb.call(this.vm, value, oldValue); }}
// 异步更新队列let queue = [];let flushing = false;let waiting = false;
function queueWatcher(watcher) { if (!queue.includes(watcher)) { queue.push(watcher); }
if (!waiting) { waiting = true; nextTick(flushQueue); }}
function flushQueue() { flushing = true; queue.forEach(w => w.run()); queue = []; flushing = false; waiting = false;}
// 将数组方法转换为响应式const arrayProto = Array.prototype;const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => { const original = arrayProto[method];
arrayMethods[method] = function(...args) { 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; };});
// 观察者类class Observer { constructor(value) { this.value = value; this.dep = new Dep();
def(value, '__ob__', this);
if (Array.isArray(value)) { value.__proto__ = arrayMethods; 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)); }}
// 响应式绑定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();
// 数组依赖收集 if (Array.isArray(val)) { dependArray(val); } } } return val; }, set(newVal) { if (newVal === val) return; val = newVal; childOb = observe(newVal); dep.notify(); } });}
function dependArray(value) { for (let i = 0, l = value.length; i < l; i++) { const e = value[i]; e && e.__ob__ && e.__ob__.dep.depend(); if (Array.isArray(e)) { dependArray(e); } }}
function observe(value) { if (!value || typeof value !== 'object') return; return new Observer(value);}
// Vue 实例简化版class Vue { constructor(options) { this.$options = options; this._data = options.data;
// 数据响应化 observe(this._data);
// 代理data到实例 Object.keys(this._data).forEach(key => { proxy(this, '_data', key); }); }
$watch(expOrFn, cb) { return new Watcher(this, expOrFn, cb); }}
// 数据代理function proxy(target, sourceKey, key) { Object.defineProperty(target, key, { get() { return target[sourceKey][key]; }, set(newVal) { target[sourceKey][key] = newVal; } });}
// 计算属性实现function computed(getter) { const watcher = new Watcher(this, getter, null, { lazy: true });
Object.defineProperty(this, key, { get() { if (watcher.dirty) { watcher.evaluate(); } return watcher.value; } });}
// 下一个tick实现function nextTick(cb) { const timerFn = () => { cb(); };
if (typeof Promise !== 'undefined') { Promise.resolve().then(timerFn); } else if (typeof MutationObserver !== 'undefined') { const observer = new MutationObserver(timerFn); const textNode = document.createTextNode('1'); observer.observe(textNode, { characterData: true }); textNode.data = '2'; } else { setTimeout(timerFn, 0); }}
使用示例
// 创建Vue实例const vm = new Vue({ data: { count: 0, items: [1, 2, 3], person: { name: 'Alice', age: 30 } }});
// 监听数据变化vm.$watch('count', (newVal, oldVal) => { console.log(`Count changed from ${oldVal} to ${newVal}`);});
// 监听数组变化vm.$watch(() => vm.items.length, (newLen, oldLen) => { console.log(`Array length changed from ${oldLen} to ${newLen}`);});
// 测试响应式vm.count++; // 输出: Count changed from 0 to 1
// 测试数组响应式vm.items.push(4); // 输出: Array length changed from 3 to 4
// 测试嵌套对象vm.person.age = 31; // 触发更新
// 测试新增属性(需要特殊处理)// Vue2 需要使用 Vue.set 或 this.$setVue.set(vm.person, 'job', 'Engineer');
// 测试计算属性vm.computed = computed(() => vm.count * 2);console.log(vm.computed); // 2 (vm.count=1)vm.count++;console.log(vm.computed); // 4
业务场景
- 数据绑定:表单输入与状态实时同步
- 复杂状态管理:Vue组件内部状态响应式更新
- 列表渲染:数组变化时自动更新DOM列表
- 计算属性:基于其他状态自动计算派生值
- 侦听器:监听数据变化执行特定逻辑
- 状态依赖:组件间状态自动同步更新
设计挑战:
- 对象属性新增/删除需要通过
Vue.set
/Vue.delete
处理- 数组变化检测需要重写原型方法
- 深层嵌套对象需要递归处理
- 异步更新队列防止重复渲染
- 计算属性的缓存优化机制
相似题目
- Vue3 响应式实现(困难) 核心:Proxy 代替 Object.defineProperty
- React Hooks 实现(困难) 核心:闭包 + 链表管理状态
- 实现观察者模式(中等) 核心:Subject/Observer 接口实现
- 实现 Promise A+规范(困难) 核心:状态机 + 异步回调队列
- 实现前端路由系统(中等) 核心:History API + 组件切换
设计局限与解决方案:
flowchart LR A[Object.defineProperty局限] --> B[无法检测对象属性添加/删除] A --> C[无法检测数组索引变更] A --> D[性能消耗较大] B --> E[Vue.set/Vue.delete API] C --> F[数组方法重写] D --> G[Vue3改用Proxy]