MST

星途 面试题库

面试题:Vue中响应式系统的依赖收集是如何实现的

在Vue的响应式系统里,依赖收集对于数据变化的追踪至关重要。请详细阐述Vue是怎样实现依赖收集的,包括涉及到的数据结构和主要的逻辑步骤。
13.8万 热度难度
前端开发Vue

知识考点

AI 面试

面试题答案

一键面试

1. 数据结构

  • Dep类:每个被侦测的数据(响应式数据)都有一个对应的Dep实例。它用于收集依赖(Watcher实例),内部使用一个Set来存储依赖,这样可以保证依赖的唯一性。例如:
class Dep {
  constructor() {
    this.subs = new Set();
  }
  addSub(watcher) {
    this.subs.add(watcher);
  }
  removeSub(watcher) {
    this.subs.delete(watcher);
  }
  notify() {
    this.subs.forEach(sub => sub.update());
  }
}
  • Watcher类:代表一个依赖,它实例化时会将自己添加到正在读取的响应式数据的Dep中。Watcher实例可以是渲染watcher(负责更新视图)、计算属性watcher或者用户自定义的watcher。它有一个update方法,当依赖的数据变化时会被调用。例如:
class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm;
    this.cb = cb;
    this.getter = parsePath(expOrFn);
    this.value = this.get();
  }
  get() {
    Dep.target = this;
    const vm = this.vm;
    let value = this.getter.call(vm, vm);
    Dep.target = null;
    return value;
  }
  update() {
    const oldValue = this.value;
    this.value = this.get();
    this.cb.call(this.vm, this.value, oldValue);
  }
}
  • Observer类:用于将普通对象转换为响应式对象,给对象的每个属性都定义gettersetter。在getter中进行依赖收集,在setter中通知依赖更新。例如:
class Observer {
  constructor(value) {
    this.value = value;
    this.walk(value);
  }
  walk(obj) {
    const keys = Object.keys(obj);
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]]);
    }
  }
}
function defineReactive(data, key, val) {
  const dep = new Dep();
  const childOb = observe(val);
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      if (Dep.target) {
        dep.addSub(Dep.target);
        if (childOb) {
          childOb.dep.addSub(Dep.target);
        }
      }
      return val;
    },
    set: function reactiveSetter(newVal) {
      if (newVal === val) return;
      val = newVal;
      childOb = observe(newVal);
      dep.notify();
    }
  });
}
function observe(value) {
  if (!value || typeof value!== 'object') {
    return;
  }
  return new Observer(value);
}

2. 主要逻辑步骤

  • 初始化阶段
    • 当Vue实例创建时,会对data选项中的数据通过Observer类进行转换,将其变为响应式数据,每个属性都关联一个Dep实例。
    • 同时,会根据模板编译生成渲染watcher,计算属性也会生成对应的计算属性watcher,用户自定义watcher通过$watch创建。这些watcher实例在创建时会立即求值(调用get方法)。
  • 依赖收集阶段
    • 以渲染watcher为例,在求值过程中(例如访问vm.message),会触发message属性的getter
    • getter中,由于Dep.target此时指向当前的渲染watcher,所以会将该watcher添加到message属性对应的Dep实例中,完成依赖收集。如果message属性值是一个对象,还会将watcher添加到这个对象对应的Observer实例的dep中(用于监听对象内属性的新增和删除)。
  • 数据更新阶段
    • 当数据发生变化(例如vm.message = 'new value'),会触发message属性的setter
    • setter中,首先检查新值与旧值是否相同,不同则更新值,并通知Dep实例。
    • Dep实例调用notify方法,遍历其收集的所有watcher实例,调用它们的update方法。
    • watcherupdate方法会重新求值(重新渲染视图或者重新计算计算属性值等),从而实现数据变化后的更新操作。