Vue 2 的依赖收集过程

时间:2024-11-02 09:54:26
<!DOCTYPE html>
<html>

<head>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>

<body>
  <div id="app">
    <p>{{ name }} is {{ age }} years old.</p>
    <button @click="updateData">Update Data</button>
  </div>

  <script>
    new Vue({
      el: '#app',
      data: {
        name: 'Alice',
        age: 30
      },
      methods: {
        updateData() {
          this.name = 'Bob';
          this.age = 35;
        }
      }
    });
  }
  // 假设这里是 Vue 的内部实现部分
  function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 依赖收集
        if (Dep.target) {
          dep.depend();
        }
        return val;
      },
      set(newVal) {
        if (val === newVal) return;
        val = newVal;
        // 触发依赖更新
        dep.notify();
      }
    });
  }
  function observe(obj) {
    if (!obj || typeof obj!== 'object') return;
    Object.keys(obj).forEach(key => {
      defineReactive(obj, key, obj[key]);
    });
  }
  class Dep {
    constructor() {
      this.subs = [];
    }
    addSub(sub) {
      this.subs.push(sub);
    }
    depend() {
      if (Dep.target) {
        this.addSub(Dep.target);
      }
    }
    notify() {
      this.subs.forEach(sub => {
        sub.update();
      });
    }
  }
  Dep.target = null;
  function watcher(fn) {
    this.fn = fn;
    this.update = () => {
      this.fn();
    };
    Dep.target = this;
    this.fn();
    Dep.target = null;
  }
  observe(new Vue().$data);
  new watcher(() => {
    console.log('依赖收集触发了此函数,数据变化会再次触发');
    document.getElementById('app').querySelector('p').textContent = `${new Vue().$data.name} is ${new Vue().$data.age} years old.`;
  });
  </script>
</body>

</html>

在上述代码中,我们模拟了 Vue 2 的依赖收集过程:
首先,在 defineReactive 函数中,当属性被读取时(即触发 get 方法),检查 Dep.target 是否存在。如果存在,说明有地方在读取这个属性,需要进行依赖收集。
在 depend 方法中,将当前的依赖(这里是一个 watcher 实例)添加到 Dep 的订阅列表中。
在 watcher 的构造函数中,将当前 watcher 实例设置为 Dep.target,然后执行传入的函数,这个函数通常是渲染函数或者计算属性的函数。在执行这个函数的过程中,如果读取了响应式数据的属性,就会触发 get 方法,从而进行依赖收集。执行完函数后,将 Dep.target 重置为 null。
当页面加载时,会执行 observe 函数对 data 对象进行响应式处理,并创建一个 watcher 实例,这个实例的构造函数会触发依赖收集过程。当模板中读取 name 和 age 的值时,就会将这个 watcher 实例添加到相应属性的依赖列表中。这样,当 name 或 age 的值发生变化时,就可以通知这个 watcher 实例进行更新。