简化版的 Vue 2 响应式系统实现
Observer:递归劫持对象属性。Dep:依赖收集和通知。Watcher:依赖收集和更新回调。defineReactive:核心的Object.defineProperty劫持。
数据读取 → 触发 getter → Dep.depend() → Watcher 被收集。
数据修改 → 触发 setter → dep.notify() → Watcher.update() → 视图更新。
// 1. Dep:依赖收集器
class Dep {
constructor() {
this.subs = []; // 存储所有依赖(Watcher)
}
// 添加依赖
addSub(watcher) {
if (watcher && !this.subs.includes(watcher)) {
this.subs.push(watcher);
}
}
// 移除依赖
removeSub(watcher) {
const index = this.subs.indexOf(watcher);
if (index > -1) {
this.subs.splice(index, 1);
}
}
// 收集当前活跃的 Watcher
depend() {
if (Dep.target) {
this.addSub(Dep.target);
}
}
// 通知所有依赖更新
notify() {
// 创建副本,避免在 notify 过程中 watcher 被修改
const subs = this.subs.slice();
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
// 全局唯一正在执行的 Watcher,用于依赖收集
Dep.target = null;
// 用于在多个 Watcher 嵌套时保存和恢复当前目标 Watcher
const targetStack = [];
function pushTarget(watcher) {
targetStack.push(Dep.target);
Dep.target = watcher;
}
function popTarget() {
Dep.target = targetStack.pop();
}
// 2. Observer:观察者,负责将对象变成响应式
class Observer {
constructor(value) {
this.value = value;
// 给对象添加一个 dep,用于处理 vm.$set 或 vm.$delete 时通知更新
this.dep = new Dep();
// 标记已观察,避免重复观察
def(value, '__ob__', this);
if (Array.isArray(value)) {
// 数组的特殊处理(这里简化,不展开)
this.observeArray(value);
} else {
this.walk(value);
}
}
// 遍历对象所有属性,转换为 getter/setter
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
}
observeArray(items) {
for (let i = 0; i < items.length; i++) {
observe(items[i]);
}
}
}
// 定义响应式属性
function defineReactive(obj, key, val) {
// 每个属性都有自己的依赖收集器
const dep = new Dep();
// 如果值是对象,递归观察
if (val && typeof val === 'object') {
observe(val);
}
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 依赖收集
if (Dep.target) {
dep.depend();
if (val && val.__ob__) {
// 如果值是对象,也收集依赖(处理 vm.$set 的情况)
val.__ob__.dep.depend();
}
}
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
// 如果新值是对象,也要变成响应式
if (newVal && typeof newVal === 'object') {
observe(newVal);
}
// 派发更新
dep.notify();
}
});
}
// 尝试观察一个值
function observe(value) {
if (!value || typeof value !== 'object') {
return;
}
// 避免重复观察
if (value.__ob__) {
return value.__ob__;
}
return new Observer(value);
}
// 辅助函数:定义不可枚举的属性
function def(obj, key, val) {
Object.defineProperty(obj, key, {
value: val,
enumerable: false,
writable: true,
configurable: true
});
}
// 3. Watcher:观察者,连接数据和视图
class Watcher {
constructor(vm, expOrFn, cb, options = {}) {
this.vm = vm;
this.cb = cb;
this.options = options;
// 存储依赖的 dep
this.deps = [];
this.newDeps = [];
this.depIds = new Set();
this.newDepIds = new Set();
// 表达式或函数(如 'a.b' 或 function() { return vm.a.b })
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
// 简化:只支持函数
this.getter = expOrFn;
}
// 获取初始值,触发依赖收集
this.value = this.get();
}
// 读取值,触发 getter 收集依赖
get() {
pushTarget(this); // 设置当前 Watcher 为 Dep.target
let value;
try {
value = this.getter.call(this.vm, this.vm);
} catch (e) {
console.error(e);
} finally {
popTarget(); // 恢复
this.cleanupDeps(); // 清理不再依赖的 dep
}
return value;
}
// 更新
update() {
// 这里可以加入异步队列(nextTick),我们简化为同步
this.run();
}
run() {
const value = this.get(); // 重新获取值
const oldValue = this.value;
this.value = value;
// 执行回调(如更新视图)
this.cb.call(this.vm, value, oldValue);
}
// 添加依赖
addDep(dep) {
const id = dep.constructor.name === 'Dep' ? dep : null; // 简化
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
}
// 清理依赖
cleanupDeps() {
let i = this.deps.length;
while (i--) {
const dep = this.deps[i];
if (!this.newDepIds.has(dep)) {
dep.removeSub(this);
}
}
const tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
const depsTmp = this.deps;
this.deps = this.newDeps;
this.newDeps = depsTmp;
this.newDeps.length = 0;
}
}
// 4. 模拟一个简单的 Vue 实例
class Vue {
constructor(options) {
this.$options = options;
this.$data = options.data;
// 将 data 代理到 vm 上
this._proxyData(this.$data);
// 观察 data
observe(this.$data);
// 创建一个渲染 Watcher(模拟视图更新)
new Watcher(this, () => {
console.log('✅ 视图更新:', this.$data.message);
// 这里可以触发虚拟 DOM 重渲染
}, null, { lazy: false });
}
_proxyData(data) {
Object.keys(data).forEach(key => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return data[key];
},
set(newVal) {
data[key] = newVal;
}
});
});
}
}