vue2

一、核心流程
- 数据劫持 :通过
Object.defineProperty递归遍历对象的每个属性,将其转换为 getter/setter。 - 依赖收集:在 getter 中收集依赖(Watcher),在 setter 中触发更新。
- 派发更新:数据变化时,通知所有依赖的 Watcher 执行更新操作(如重新渲染视图)。
二、关键模块
1. Observer(观察者)
- 递归遍历数据对象,为每个属性添加 getter/setter。
- 若属性值为对象,则递归调用
Observer继续劫持。 - 对数组类型,重写数组的
push、pop等原型方法,以监听数组变化。
只是定义了get/set的方法,没有触发,所以就没有进行dep的watcher收集
js
class Observer {
constructor(data) {
this.walk(data);
}
walk(data) {
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key]);
});
}
}
function defineReactive(obj, key, val) {
// 递归处理嵌套对象
if (typeof val === 'object') new Observer(val);
const dep = new Dep(); // 每个属性对应一个 Dep 实例
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) { // 当前 Watcher(依赖)
dep.addSub(Dep.target); // 收集依赖
}
return val;
},
set(newVal) {
if (val === newVal) return;
val = newVal;
dep.notify(); // 通知所有 Watcher 更新
}
});
}
2. Dep(依赖管理器)
- 每个被劫持的属性都有一个对应的
Dep实例。 - 用于收集和管理所有依赖于该属性的 Watcher。
data中的每一个数据,都有一个_dep属性,来收集watcher
Dep.target是一个全局的静态属性,为了进行watcher的赋值
js
class Dep {
constructor() {
this.subs = []; // 存储 Watcher
}
addSub(watcher) {
this.subs.push(watcher);
}
notify() {
this.subs.forEach(watcher => watcher.update());
}
}
Dep.target = null; // 全局变量,指向当前正在计算的 Watcher
3. Watcher(观察者)
- 作为 Observer 和 Compiler 的桥梁。
- 数据变化时触发
update方法,执行回调(如更新视图)。
js
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this; // 设置当前 Watcher
this.vm[this.key]; // 触发 getter,收集依赖
Dep.target = null; // 收集完成后清空
}
update() {
this.cb.call(this.vm, this.vm[this.key]);
}
}
4.初始化数据流程
伪代码,初始化单个vue模板,响应式的部分工作流程
js
data(){
return {
xxx:1
}
}
// 此时,所有数据都被改造,设置了set和get,也初始化了dep依赖管理器为 []
new Observer(data())
// 循环全部的data,进行watcher的依赖收集
for (let i = 0; i < data().length; i++) {
new Watcher(data[i])
}
data.xxx = 2; // 触发watcher,更新dom
三、数组响应式处理
由于 Object.defineProperty 无法监听数组索引变化,Vue2 通过重写数组方法实现响应式:
js
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__; // Observer 实例
ob.dep.notify(); // 触发更新
return result;
};
});
四、缺陷与限制
- 无法检测新增/删除的属性 :
需使用Vue.set()或Vue.delete()进行响应式处理。 - 数组索引和长度变化无法监听 :
通过重写数组方法解决索引变更,但直接通过索引赋值(arr[0] = 1)或修改length仍无法监听。 - 递归性能消耗 :
初始化时需要递归遍历所有属性,对复杂对象性能开销较大。
五、编写vue2代码的性能优化
少写data中的属性,或者禁用部分属性的响应式
vue3

执行流程
js
setup(){
let state = ref({
count:1111
})
effect(
() => {
console.log(`副作用:count = ${state.count}`);
},
{
scheduler: () => {
console.log("state.count改变");
}
}
)
state.value.count ++
}
全局数据
js
// 全局依赖存储结构
const targetMap = new WeakMap(); // target -> depsMap
let activeEffect = null; // 当前活跃的副作用
一、类比vue3中的Observer(观察者)
只是定义了get/set的方法,没有触发,所以就没有进行dep的Effect副作用收集
javascript
// 创建响应式对象
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
// 依赖收集
track(target, key);
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;
}
});
}
js
// 依赖收集:建立target.key与当前activeEffect的关联
function track(target, key) {
if (!activeEffect) return;
// 1. 从targetMap获取target对应的depsMap
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// 2. 从depsMap获取key对应的dep
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
// 3. 将当前活跃的effect添加到dep中
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
// 同时,effect也需要记录自己被哪些dep收集(用于清理)
activeEffect.deps.push(dep);
}
}
js
// 触发更新:当target.key发生变化时,执行所有相关的effect
function trigger(target, key) {
// 1. 从targetMap获取target对应的depsMap
const depsMap = targetMap.get(target);
if (!depsMap) return;
// 2. 从depsMap获取key对应的dep
const dep = depsMap.get(key);
if (!dep) return;
// 3. 执行dep中所有effect
// 注意:这里要创建副本,因为在执行effect时可能会修改原dep
const effectsToRun = new Set(dep);
effectsToRun.forEach(effect => {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
});
}
二、类比vue2中的Dep(依赖管理器)
js
// 副作用类
class ReactiveEffect {
constructor(fn, scheduler = null) {
this.fn = fn;
this.scheduler = scheduler;
this.active = true;
this.deps = []; // 存储所有依赖这个effect的dep集合
}
run() {
if (!this.active) return;
// 保存当前活跃的副作用
const lastActiveEffect = activeEffect;
activeEffect = this;
try {
return this.fn();
} finally {
// 恢复之前的活跃副作用
activeEffect = lastActiveEffect;
}
}
stop() {
if (this.active) {
cleanupEffect(this);
this.active = false;
}
}
}
三、类比vue2中的Dep.target
js
// 对嵌套的effect优化
let activeEffect = null; // 当前活跃的副作用
const lastActiveEffect = activeEffect;
activeEffect = lastActiveEffect;
四、创建副作用,类似vue2中的watcher,执行fn,读取数据,收集dep
js
// 创建副作用
function effect(fn, options = {}) {
const _effect = new ReactiveEffect(fn, options.scheduler);
_effect.run();
// 返回一个runner,可以手动执行或停止effect
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}