vue2 数据数据双向绑定原理

发布于:2025-05-15 ⋅ 阅读:(12) ⋅ 点赞:(0)

一、核心实现原理

1. 数据劫持(Data Observation)

Vue 2 通过 Object.defineProperty 对对象属性进行劫持,将其转换为 getter/setter,从而在数据被访问或修改时触发依赖收集和更新通知。

function defineReactive(obj, key, val) {
  // 递归劫持嵌套对象
  observe(val); 

  const dep = new Dep(); // 每个属性对应一个依赖管理器

  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {       // 当前正在计算的 Watcher(依赖)
        dep.depend();         // 将 Watcher 添加到依赖列表
      }
      return val;
    },
    set(newVal) {
      if (newVal === val) return;
      val = newVal;
      dep.notify();           // 通知所有依赖更新
    }
  });
}

// 遍历对象所有属性进行劫持
function observe(obj) {
  if (typeof obj !== 'object' || obj === null) return;
  new Observer(obj);
}

二、依赖收集与派发更新

1. 依赖收集(Dep 类)
  • Dep (Dependency):每个响应式属性对应一个 Dep 实例,用于管理所有依赖它的 Watcher
    class Dep {
      constructor() {
        this.subs = []; // 存储 Watcher 列表
      }
      depend() {
        if (Dep.target) {
          this.subs.push(Dep.target); // 添加当前 Watcher
        }
      }
      notify() {
        this.subs.forEach(watcher => watcher.update()); // 触发更新
      }
    }
    
2. 观察者(Watcher 类)
  • Watcher:代表一个具体的依赖(如组件渲染函数、计算属性),当数据变化时触发回调更新视图。
    class Watcher {
      constructor(vm, key, cb) {
        Dep.target = this;     // 标记当前 Watcher
        this.cb = cb;
        vm._data[key];         // 触发 getter 收集依赖
        Dep.target = null;     // 重置
      }
      update() {
        this.cb();             // 执行更新回调
      }
    }
    

三、双向绑定实现(v-model)

v-model 是语法糖,本质是 value 属性绑定 + input 事件监听的组合:

<!-- 模板 -->
<input v-model="message">
<!-- 等价于 -->
<input 
  :value="message" 
  @input="message = $event.target.value"
>
实现步骤:
  1. 初始化阶段:通过 Object.defineProperty 劫持 messagegetter/setter
  2. 模板编译:解析 v-model,为 input 元素添加 value 属性和 input 事件监听。
  3. 输入事件触发:用户输入时,通过 input 事件修改 message 的值。
  4. 响应式更新messagesetter 触发,通知所有依赖的 Watcher 更新视图。

四、数组的响应式处理

Vue 2 无法直接监听数组索引操作,通过 重写数组方法 实现响应式:

const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
// 重写变异方法(push/pop/shift/unshift/splice/sort/reverse)
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
  const original = arrayProto[method];
  def(arrayMethods, method, function(...args) {
    const result = original.apply(this, args);
    const ob = this.__ob__;
    ob.dep.notify(); // 手动触发更新
    return result;
  });
});

五、Vue 2 双向绑定的局限性

  1. 对象属性新增/删除无法检测
    需通过 Vue.set(obj, 'newKey', value)Vue.delete(obj, 'key') 触发更新。

  2. 数组索引和长度修改无法监听
    直接通过索引修改元素(如 arr[0] = 1)或修改 length 属性不会触发响应式。

  3. 性能问题
    初始化时递归遍历所有属性,大型对象性能较差。

六、手动触发更新的场景

// 对象新增属性(非响应式)
this.user.profile = { age: 25 }; // ❌ 不触发更新
Vue.set(this.user, 'profile', { age: 25 }); // ✅ 触发更新

// 数组索引修改(非响应式)
this.items[0] = 'new'; // ❌ 不触发更新
Vue.set(this.items, 0, 'new'); // ✅ 触发更新

Vue 2 双向绑定流程

  1. 数据初始化:递归遍历对象,通过 Object.defineProperty 劫持属性。
  2. 模板编译:解析模板中的 v-model 指令,绑定 value 属性和 input 事件。
  3. 依赖收集:渲染过程中触发 getter,将 Watcher 注册到 Dep
  4. 数据更新:用户输入触发 input 事件修改数据,setter 通知 Dep 派发更新。
  5. 视图更新Watcher 执行回调,触发组件重新渲染。

网站公告

今日签到

点亮在社区的每一天
去签到