vue2/3响应式原理

vue2

一、核心流程

  1. 数据劫持 :通过 Object.defineProperty 递归遍历对象的每个属性,将其转换为 getter/setter
  2. 依赖收集:在 getter 中收集依赖(Watcher),在 setter 中触发更新。
  3. 派发更新:数据变化时,通知所有依赖的 Watcher 执行更新操作(如重新渲染视图)。

二、关键模块

1. Observer(观察者)

  • 递归遍历数据对象,为每个属性添加 getter/setter。
  • 若属性值为对象,则递归调用 Observer 继续劫持。
  • 对数组类型,重写数组的 pushpop 等原型方法,以监听数组变化。
只是定义了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(观察者)

  • 作为 ObserverCompiler 的桥梁。
  • 数据变化时触发 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;
  };
});

四、缺陷与限制

  1. 无法检测新增/删除的属性
    需使用 Vue.set()Vue.delete() 进行响应式处理。
  2. 数组索引和长度变化无法监听
    通过重写数组方法解决索引变更,但直接通过索引赋值(arr[0] = 1)或修改 length 仍无法监听。
  3. 递归性能消耗
    初始化时需要递归遍历所有属性,对复杂对象性能开销较大。

五、编写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;
}
相关推荐
不务正业的前端学徒2 小时前
vue2/3computed原理
前端
前端付豪2 小时前
NodeJs 做了什么 Fundamentals Internals
前端·开源·node.js
爱分享的鱼鱼2 小时前
Pinia 数据跨组件共享机制与生命周期详解
前端
张元清2 小时前
大白话讲 React2Shell 漏洞:智能家居的语音助手危机
前端·javascript·react.js
wuhen_n2 小时前
手写符合A+规范的Promise
前端
小明记账簿_微信小程序2 小时前
一篇文章教会你接入Deepseek API
前端
若凡SEO2 小时前
深圳优势产业(电子 / 机械)出海独立站运营白皮书
大数据·前端·搜索引擎
踢球的打工仔2 小时前
typescript-void和never
前端·javascript·typescript
hugo_im2 小时前
GrapesJS 完全指南:从零构建你的可视化拖拽编辑器
前端·javascript·前端框架