写在前面:本文为个人在日常工作和学习中的一些总结,便于后来查漏补缺,非权威性资料,请带着自己的思考^-^。
前文链接:了解一下Vue - [Vue是怎么实现响应式的(一)]
了解一下Vue - [Vue是怎么实现响应式的(二)]
前面对于响应式有了一些些了解,这里尝试自己写一遍(抄一遍)
设对象data为要被定义的响应式对象,key为data中的属性

  1. 通过defineProperty为data中的每一个属性key定义getter/setter,这样在data[key]被引用和被赋值时将触发相应的操作,这个是响应式实现的基础;
  2. dep对象,作为依赖实体,其包含依赖添加、依赖者(watcher)添加、变更通知(notify)等方法,用于依赖收集、变更分发,本身包含观察者列表;
  3. watcher对象,观察者对象,包含依赖收集、更新方法;用于依赖收集、更新,包含依赖列表;

Dep构造函数

  let uid = 0;

  class Dep {
    constructor() {
      this.id = ++uid;
      this.subs = [];
    }
    addSub(target) { // 将target添加进观察者列表
      this.subs.push(target);
    }
    depend() { // 
      if (Dep.target) {
        Dep.target.addDep(this);
      }
    }
    removeSub(target) {
      this.subs.splice(this.subs.findIndex(_ => _.id == target.id), 1);
    }
    notify() {
      const subs = this.subs.slice();
      for (let i = 0; i < subs.length; i++) {
        subs[i].update();
      }
    }
  }

  Dep.target = null; // 当前处于激活状态的watcher
  const targetStack = []; // 存放watcher的栈

  const pushTarget = target => { // 更新当前激活的Dep.target
    if (Dep.target) {
      targetStack.push(target); // 将target(watcher)压入栈中
    }
    Dep.target = target;
  };

  const popTarget = () => { // 从watcher栈中弹出顶部watcher
    Dep.target = targetStack.pop();
  };

watcher构造函数

  class Watcher {
    constructor(getter, options = {}) {
      this.deps = []; // 依赖列表
      this.newDeps = []; // 最近一次添加的依赖列表
      this.depIds = new Set(); // 依赖ids列表
      this.newDepIds = new Set(); // 最近一次添加的依赖ids列表
      this.getter = getter; // 
      this.lazy = !!options.lazy; // 懒依赖,在首次实例化的时候不执行getter
      this.dirty = this.lazy; // 脏值标识,主要用在computed计算时;
      this.lazy ? undefined : this.get();
    }
    get() {
      pushTarget(this); // 将当前watcher作为激活的watcher对象,并推入targetStack栈中
      const value = this.getter();
      popTarget(); // 将当前watcher置为栈中上一个watcher
      this.cleanupDeps(); // 依赖整理,主要用来整理this.deps、this.depIds
      return value;
    }
    addDep(dep) {
      if (!this.newDepIds.has(dep.id)) {
        this.newDepIds.add(dep.id);
        this.newDeps.push(dep);
        if (!this.depIds.has(dep.id)) {
          dep.addSub(this);
        }
      }
    }
    cleanupDeps() {
      let i = this.deps.length;
      while (i--) {
        const dep = this.deps[i];
        if (!this.newDepIds.has(dep.id)) { // 如果新的依赖列表中不再包含之前的依赖项,则调用dep.removeSub方法,将当前watcher从dep.subs列表中移除
          dep.removeSub(this);
        }
      }
      [this.deps, this.newDeps] = [this.newDeps, this.deps];
      this.newDeps.length = 0;
      [this.depIds, this.newDepIds] = [this.newDepIds, this.depIds];
      this.newDepIds.clear();
    }
    evaluate() {
      this.value = this.get();
      this.dirty = false;
    }
    update() {
      if (this.lazy) {
        this.dirty = true;
      } else {
        new Promise((resolve) => {
          resolve();
        }).then(() => {
          this.get();
        });
      }
    }
    depend() { // 将当前watcher的依赖添加到当前Dep.target的依赖列表中
      const deps = this.deps;
      for (let i = 0; i < deps.length; i++) {
        deps[i].depend();
      }
    }
  }

defineReactive 方法

用来为data[key]定义getter/setter

  const defineReactive = (target, key, val) => {
    const dep = new Dep(); // 这里实例化dep对象,用于在getter/setter触发的时候访问该对象进行依赖收集等操作,本质上来说当前实例化的dep和当前的data[key]一一对应了
    Object.defineProperty(target, key, {
      enumerable: true,
      configurable: true,
      get() {
        if (Dep.target) {
          dep.depend(); // 依赖添加
        }
        return val;
      },
      set(newVal) {
        if (val === newVal) return;
        val = newVal;
        dep.notify();
      },
    });
  };

data、computed初始化

  const initData = data => {
    const keys = Object.keys(data);
    for (let i = 0; i < keys.length; i++) {
      defineReactive(vm, keys[i], data[keys[i]]);
    }
  };

  const initComputed = computed => {
    const keys = Object.keys(computed);
    for (let i = 0; i < keys.length; i++) {
      const userDef = computed[keys[i]].bind(vm);
      const watcher = new Watcher(userDef, { lazy: true });

      Object.defineProperty(vm, keys[i], {
        enumerable: true,
        configurable: true,
        get() {
          if (watcher) {
            if (watcher.dirty) {
              watcher.evaluate();
            }
            if (Dep.target) {
              watcher.depend();
            }
            return watcher.value;
          }
        },
      });
    }
  };

模拟页面渲染:

<!-- html -->
<body>
  <div id="app"></div>
  <button id="btn">更新</button>
</body>
  const data = initData(vm.data);

  const computed = initComputed(vm.computed);

  const updateComponent = () => {
    const app = document.querySelector('#app');
    var a = vm.current; // 引用vm.current
    var b = vm.computedCurrent; // 应用computed
    app.innerHTML = `data: <i>${a}</i> computed: <strong>${b}</strong>`;
  };

  defineReactive(vm, 'current', vm.current); // 为vm.current定义getter/setter
  const watcher = new Watcher(updateComponent);

可以看到点击按钮,更新vm中的current属性,页面成功进行了更新。。。

流程分析

初始化data属性getter/setter --> 实例化watcher,带有update方法、addDep方法 --> watcher.get方法执行进行依赖收集(该方法中被引用到的data属性视为当前watcher的依赖) --> watcher依赖收集的过程中被依赖的data属性也会进行观察者收集 --> data属性更新 --> 通知watcher,update方法调用 --> 新一轮的依赖收集,新旧依赖比较,新依赖相对旧依赖缺失的从依赖列表中删除,新增的加入依赖列表同时将观察者watcher添加进该依赖的subs观察者列表中 --> 执行业务代码(视图更新);

对于computed来说,它既是观察者,也是依赖;视图更新watcher依赖computed,computed依赖data;

对于watch来说,它是观察者,依赖要watch的data或者computed,依赖更新时会notify(通知)它,执行相应的方法;

原理大抵如此,细节还有很多

THE END


innocence
11 声望1 粉丝

undefined