Hi FE !
Ai
git
前端面试题
前端小tip
  • vite
  • webpack
npm
  • vue2
  • vue3
react
GitHub
Ai
git
前端面试题
前端小tip
  • vite
  • webpack
npm
  • vue2
  • vue3
react
GitHub
  • 简述数据绑定的原理

简述数据绑定的原理

Object.defineProperty 可以检测到属性的变化。一旦数据发生访问或修改时候,通过数据劫持,我们就可以检测到,

到底层检测到数据变化的时候,就会通过发布订阅者模式通知更新dom

Vue 的响应式原理是 Vue.js 框架的核心机制,它使得数据变化能够自动更新视图。其核心是利用 JavaScript 的 Object.defineProperty(Vue 2.x)或 Proxy(Vue 3.x)来实现数据劫持,结合发布-订阅模式,在数据变化时通知依赖进行更新。 下面分别对 Vue 2 和 Vue 3 的响应式原理进行说明:

Vue 2.x 响应式原理

  1. 数据劫持(Data Observation):
    • Vue 2 使用Object.defineProperty来劫持对象属性的 getter 和 setter。
    • 遍历数据对象的所有属性,将其转换为 getter/setter 形式,从而在读取和设置时可以执行自定义操作。
  2. 依赖收集(Dependency Collection):
    • 在 getter 中收集依赖(Watcher)。每个组件实例对应一个 Watcher,Watcher 会在组件渲染过程中“接触”用到的数据属性,从而触发 getter,将 Watcher 添加到当前属性的依赖列表中。
    • 每个属性都有一个 Dep(依赖管理器)实例,用于存储所有依赖于该属性的 Watcher。
  3. 派发更新(Dispatching Updates):
    • 当数据发生变化时,触发 setter,调用 Dep 的 notify 方法,通知所有依赖的 Watcher 进行更新。
    • Watcher 更新会触发组件的重新渲染(重新执行 render 函数生成虚拟 DOM,然后进行 patch 更新真实 DOM)。
  4. 数组处理:
    • 由于Object.defineProperty无法监听数组索引的变化,Vue 2 通过重写数组的 7 个变更方法(push, pop, shift, unshift, splice, sort, reverse)来实现响应式。这些方法被重写,在调用时会触发更新。
  5. 缺点:
    • 无法检测对象属性的添加或删除(需要使用Vue.set或Vue.delete)。
    • 数组的索引直接设置(如arr[index]=newValue)和修改数组长度无法检测。
    • 深度监听需要递归遍历整个对象,性能负担较大。

Vue 3.x 响应式原理

Vue 3 使用 ES6 的 Proxy 代替了Object.defineProperty,解决了 Vue 2 的一些限制。

  1. Proxy 代理:
    • Proxy 可以拦截整个对象,包括属性的读取、设置、删除、方法调用等,不需要遍历每个属性。
    • 对于数组,Proxy 可以监听索引变化和length变化。
  2. Reflect:
    • 通常与 Proxy 配合使用,用于操作目标对象。
  3. 响应式 API:
    • reactive:创建响应式对象(使用 Proxy)。
    • ref:创建基本类型的响应式数据(内部也是用 reactive 包装为对象)。
    • effect:副作用函数,类似于 Vue 2 的 Watcher,当依赖的响应式数据变化时,effect 会重新执行。
  4. 依赖收集与触发更新:
    • 在 Proxy 的 get 拦截中收集依赖(track 函数)。
    • 在 set、deleteProperty 等拦截中触发更新(trigger 函数)。
    • 依赖管理采用 WeakMap(target -> Map(key -> Set(effects)))的数据结构,可以更精确地管理依赖。
  5. 优势:
    • 可以检测对象属性的添加和删除,不需要特殊 API。
    • 支持数组索引和长度的直接修改。
    • 性能更好:惰性收集,不需要一开始就递归整个对象(按需收集)。
    • 支持 Map、Set 等新的集合类型。

响应式原理的流程图(Vue 3 为例)

初始化响应式数据(reactive/ref) → 用Proxy代理对象
                                   │
读取数据(触发get) → 依赖收集(track) → 将当前activeEffect(副作用)添加到依赖集合中
                                   │
修改数据(触发set) → 依赖触发(trigger) → 从依赖集合中取出所有effect并执行
                                   │
                                   → 组件更新/副作用函数重新执行

示例代码(Vue 3 响应式模拟)

// 简单的Vue3响应式模拟
let activeEffect = null;
const targetMap = new WeakMap();
function track(target, key) {
  if (activeEffect) {
    let depsMap = targetMap.get(target);
    if (!depsMap) {
      depsMap = new Map();
      targetMap.set(target, depsMap);
    }
    let dep = depsMap.get(key);
    if (!dep) {
      dep = new Set();
      depsMap.set(key, dep);
    }
    dep.add(activeEffect);
  }
}
function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const dep = depsMap.get(key);
  if (dep) {
    dep.forEach((effect) => effect());
  }
}
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;
    },
  });
}
function effect(fn) {
  activeEffect = fn;
  fn(); // 执行一次,触发依赖收集
  activeEffect = null;
}
// 使用示例
const state = reactive({ count: 0 });
effect(() => {
  console.log(`count is: ${state.count}`);
});
state.count++; // 触发更新,输出: count is: 1

总结

  • Vue 2:通过Object.defineProperty劫持数据,需要递归遍历对象,对数组有特殊处理。使用 Dep 和 Watcher 进行依赖管理。
  • Vue 3:使用 Proxy 和 Reflect,可以拦截更多操作,无需递归初始化,性能更好,支持更多数据类型。依赖管理采用 WeakMap、Map、Set 的数据结构。 Vue 的响应式系统是其数据驱动视图的核心,理解其原理有助于更好地使用 Vue 和进行性能优化。
Edit this page
最近更新: 2025/10/17 02:02
Contributors: qdleader
qdleader
本站总访问量 129823次 | 本站访客数 12人