Vue的依赖收集


让数据变得可观测

我们都知道 Vue2 中是通过Object.defineProperty来实现的数据劫持,也就是让数据变得可观测 例如:

let car = {};
let val = 3000;
Object.defineProperty(car, "price", {
  enumerable: true,
  configurable: true,
  get() {
    console.log("price属性被读取了");
    return val;
  },
  set(newVal) {
    console.log("price属性被修改了");
    val = newVal;
  },
});

依赖收集

让 object 数据变的可观测。变的可观测以后,我们就能知道数据什么时候发生了变化,那么当数据发生变化时,我们去通知视图更新就好了。那么问题又来了,视图那么大,我们到底该通知谁去变化?总不能一个数据变化了,把整个视图全部更新一遍吧,这样显然是不合理的。此时,你肯定会想到,视图里谁用到了这个数据就更新谁呗。对!你想的没错,就是这样.

在 Vvue 中是通过实现的一个 Dep 类来实现依赖收集 源码如下

// 源码位置:src/core/observer/dep.js
export default class Dep {
  constructor() {
    this.subs = [];
  }

  addSub(sub) {
    this.subs.push(sub);
  }
  // 删除一个依赖
  removeSub(sub) {
    remove(this.subs, sub);
  }
  // 添加一个依赖
  depend() {
    if (window.target) {
      this.addSub(window.target);
    }
  }
  // 通知所有依赖更新
  notify() {
    const subs = this.subs.slice();
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update();
    }
  }
}

/**
 * Remove an item from an array
 */
export function remove(arr, item) {
  if (arr.length) {
    const index = arr.indexOf(item);
    if (index > -1) {
      return arr.splice(index, 1);
    }
  }
}

在上面的依赖管理器 Dep 类中,我们先初始化了一个 subs 数组,用来存放依赖,并且定义了几个实例方法用来对依赖进行添加,删除,通知等操作。

有了依赖管理器后,我们就可以在 getter 中收集依赖,在 setter 中通知依赖更新了,代码如下:

function defineReactive(obj, key, val) {
  if (arguments.length === 2) {
    val = obj[key];
  }
  if (typeof val === "object") {
    new Observer(val);
  }
  const dep = new Dep(); //实例化一个依赖管理器,生成一个依赖管理数组dep
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      dep.depend(); // 在getter中收集依赖
      return val;
    },
    set(newVal) {
      if (val === newVal) {
        return;
      }
      val = newVal;
      dep.notify(); // 在setter中通知依赖更新
    },
  });
}

在上述代码中,我们在 getter 中调用了 dep.depend()方法收集依赖,在 setter 中调用 dep.notify()方法通知所有依赖更新

Watcher

谁用到了数据,谁就是依赖,我们就为谁创建一个Watcher实例。在之后数据变化时,我们不直接去通知依赖更新,而是通知依赖对应的 Watch 实例,由Watcher实例去通知真正的视图

export default class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm;
    this.cb = cb;
    this.getter = parsePath(expOrFn);
    this.value = this.get();
  }
  get() {
    window.target = this;
    const vm = this.vm;
    let value = this.getter.call(vm, vm);
    window.target = undefined;
    return value;
  }
  update() {
    const oldValue = this.value;
    this.value = this.get();
    this.cb.call(this.vm, this.value, oldValue);
  }
}

/**
 * Parse simple path.
 * 把一个形如'data.a.b.c'的字符串路径所表示的值,从真实的data对象中取出来
 * 例如:
 * data = {a:{b:{c:2}}}
 * parsePath('a.b.c')(data)  // 2
 */
const bailRE = /[^\w.$]/;
export function parsePath(path) {
  if (bailRE.test(path)) {
    return;
  }
  const segments = path.split(".");
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return;
      obj = obj[segments[i]];
    }
    return obj;
  };
}

Watcher先把自己设置到全局唯一的指定位置(window.target),然后读取数据。因为读取了数据,所以会触发这个数据的 getter。接着,在 getter 中就会从全局唯一的那个位置读取当前正在读取数据的 Watcher,并把这个 watcher 收集到 Dep 中去。收集好之后,当数据发生变化时,会向 Dep 中的每个 Watcher 发送通知。通过这样的方式,Watcher 可以主动去订阅任意一个数据的变化

以上,就彻底完成了对 Object 数据的侦测,依赖收集,依赖的更新等所有操作


文章作者: Yang · Mr
版權聲明: 本部落格所有文章除特別聲明外,均採用 CC BY 4.0 許可協議。轉載請註明來源 Yang · Mr !
评论
  目錄