【vue篇】Vue 响应式原理:从数据到视图的自动同步

Vue.js 的核心魅力之一,就是它的 响应式系统(Reactive System)。

你只需修改数据,视图便会自动更新。这背后究竟是如何实现的?

本文将深入剖析 Vue 2 和 Vue 3 的响应式原理,带你理解 data 是如何"活"起来的。


一、Vue 2 的响应式原理:Object.defineProperty

✅ 核心流程图

objectivec 复制代码
data → defineProperty(getter/setter) → 依赖收集 → 派发更新 → 视图刷新

✅ 1. 初始化阶段:数据劫持

当创建 Vue 实例时,Vue 会遍历 data 中的所有属性,使用 Object.defineProperty 将它们转换为 getter/setter。

js 复制代码
function defineReactive(obj, key, val) {
  // 递归处理嵌套对象
  observe(val);

  const dep = new Dep(); // 依赖收集器

  Object.defineProperty(obj, key, {
    get() {
      // 依赖收集:谁在读取这个值?
      if (Dep.target) {
        dep.depend();
      }
      return val;
    },
    set(newVal) {
      if (newVal === val) return;
      val = newVal;
      // 新值也可能是对象,需要继续劫持
      observe(newVal);
      // 派发更新:通知所有依赖更新
      dep.notify();
    }
  });
}

✅ 2. 依赖收集(Dependency Collection)

  • 每个响应式数据都有一个 依赖收集器(Dep);
  • 当组件渲染时,会读取 data 中的属性(触发 getter);
  • 此时,Dep 会将当前正在渲染的 Watcher 记录为依赖。
js 复制代码
class Dep {
  constructor() {
    this.subs = []; // 存储所有依赖(Watcher)
  }

  depend() {
    if (Dep.target) {
      this.subs.push(Dep.target);
    }
  }

  notify() {
    this.subs.forEach(watcher => watcher.update());
  }
}

Dep.target 是当前正在执行的 Watcher(通过栈管理)。


✅ 3. 派发更新(Notify Update)

  • 当数据被修改时,触发 setter;
  • setter 调用 dep.notify();
  • 所有依赖该数据的 Watcher 收到通知,执行 update()。

✅ 4. Watcher:连接数据与视图的桥梁

每个组件实例对应一个 Watcher 实例。

js 复制代码
class Watcher {
  constructor(vm, exprOrFn, cb) {
    this.vm = vm;
    this.getter = exprOrFn; // 例如:render function
    this.cb = cb;
    this.value = this.get(); // 首次执行,触发 getter,完成依赖收集
  }

  get() {
    Dep.target = this; // 设置当前 Watcher
    const value = this.getter.call(this.vm, this.vm);
    Dep.target = null; // 清除
    return value;
  }

  update() {
    const oldValue = this.value;
    this.value = this.get(); // 重新执行,获取新值
    this.cb.call(this.vm, this.value, oldValue);
  }
}

✅ 完整流程演示

js 复制代码
new Vue({
  data: {
    message: 'Hello Vue'
  },
  template: `<div>{{ message }}</div>`
});
  1. 初始化 :message 被 defineProperty 劫持;

  2. 首次渲染 :

    • 执行 render 函数,读取 message;
    • 触发 getter,Dep 收集当前组件的 Watcher;
  3. 数据更新 :

    js 复制代码
    vm.message = 'Hello World';
    • 触发 setter;
    • dep.notify() 通知 Watcher;
    • Watcher 重新执行 render,视图更新。

二、Vue 3 的响应式原理:Proxy

❌ Vue 2 的局限性

  • 无法检测属性的添加或删除;
  • 数组方法(如 push、splice)需要特殊处理;
  • 初始化时需要递归遍历 data,性能开销大。

✅ Vue 3 的解决方案:Proxy

js 复制代码
const reactive = (obj) => {
  return new Proxy(obj, {
    get(target, key, receiver) {
      // 依赖收集
      track(target, key);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      // 派发更新
      trigger(target, key);
      return result;
    },
    deleteProperty(target, key) {
      const result = Reflect.deleteProperty(target, key);
      trigger(target, key);
      return result;
    }
  });
};

✅ Vue 3 的优势

特性 Vue 2 (defineProperty) Vue 3 (Proxy)
检测属性新增/删除 ❌ 需 vm.$set ✅ 原生支持
检测数组索引修改 ❌ 特殊处理 ✅ 原生支持
性能 初始化递归劫持 惰性劫持(访问时才代理)
兼容性 IE9+ IE 不支持

三、响应式系统的核心模块

模块 作用
Observer 遍历 data,递归添加响应式
Dep 依赖收集器,管理 Watcher 列表
Watcher 观察者,连接数据与视图
Compiler 编译模板,生成 render 函数(Vue 2)

在 Vue 3 中,Dep 和 Watcher 被 effect 和 track/trigger 取代,逻辑更清晰。


四、常见问题与注意事项

❌ Vue 2 中的注意事项

js 复制代码
// 1. 属性新增
vm.obj.newProp = 'hi'; // ❌ 不会触发更新
this.$set(vm.obj, 'newProp', 'hi'); // ✅

// 2. 数组索引修改
vm.items[0] = 'new'; // ❌
vm.$set(vm.items, 0, 'new'); // ✅
// 或使用 splice
vm.items.splice(0, 1, 'new');

// 3. 删除属性
delete vm.obj.prop; // ❌
this.$delete(vm.obj, 'prop'); // ✅

✅ Vue 3 中的改进

js 复制代码
const state = reactive({ count: 0 });

// 所有操作天然响应式
state.count++;
state.newProp = 'hi';
delete state.count;

💡 结语

"Vue 的响应式系统,是数据驱动视图的魔法引擎。"

  • Vue 2 :基于 Object.defineProperty,通过 Dep-Watcher 模式实现;
  • Vue 3 :基于 Proxy,更强大、更高效。

理解响应式原理,不仅能帮助你:

  • 避免常见响应式陷阱;
  • 写出更高效的代码;
  • 在面试中脱颖而出。
相关推荐
颜进强1 分钟前
21 · NestJs AsyncProviders 异步提供者:useFactory 返回 Promise 之后,容器发生了什么
前端·后端·ai编程
莪_幻尘18 分钟前
Agent 体检:乱编、连锁、失忆,给 Agent 做一次五维体检
前端·人工智能·agent
秋天的一阵风30 分钟前
🚀 nacos-web-config:运营半夜改条配置,网页秒更新 —— 不用发版、不用轮询,我把它开源了
java·前端·开源
不一样的少年_1 小时前
别用前端思维写后端:一张 5MB 图片,为什么能撑爆内存?
前端·后端·图片资源
沐言人生1 小时前
太吊了!这 2 款网盘工具,让我省 4000 大洋!
前端
鬼手点金1 小时前
opencode-隐私优先配置
服务器·前端·javascript·bug·openclaw
涛涛ing1 小时前
4200万月活、85万行代码,Lovable用6个月把生产应用从Next.js迁到了TanStack Start
前端
悠仁さん1 小时前
【C++】auto
java·前端·c++
liangshanbo12151 小时前
面试题:React.memo 和 useMemo 的区别
前端·react.js·前端框架
苹果二1 小时前
使用Vue的指南
前端·javascript·vue.js