【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. 初始化messagedefineProperty 劫持;

  2. 首次渲染

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

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

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

❌ Vue 2 的局限性

  • 无法检测属性的添加或删除
  • 数组方法(如 pushsplice)需要特殊处理;
  • 初始化时需要递归遍历 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 中,DepWatchereffecttrack/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,更强大、更高效。

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

  • 避免常见响应式陷阱;
  • 写出更高效的代码;
  • 在面试中脱颖而出。
相关推荐
LuckySusu3 小时前
【vue篇】Vue 双向数据绑定原理解析:从 MVVM 到响应式视图
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 插槽(Slot)完全指南:内容分发的艺术
前端·vue.js
LuckySusu3 小时前
【vue篇】前端架构三巨头:MVC、MVP、MVVM 全面对比
前端·vue.js
开心不就得了3 小时前
css、dom 性能优化方向
前端·性能优化
道可到3 小时前
一个属性,让无数前端工程师夜不能寐
前端
闲云S3 小时前
Lit开发:字体图标的使用
前端·web components·icon
我是天龙_绍3 小时前
uniapp 个人中心页面开发指南
前端
刘永胜是我3 小时前
解决React热更新中"process is not defined"错误:一个稳定可靠的方案
前端·javascript
星链引擎3 小时前
开发者深度版(面向技术人员 / 工程师)
前端