Vue2-computed实现原理

63 阅读3分钟

在 Vue.js 中,计算属性是通过 Watcher 实例的lazydirtyvalue属性来实现自动更新和缓存的。在执行render函数时,会调用计算属性的getter方法,根据dirty值来决定是计算最新值还是返回缓存值。数据变化时,会依次触发计算属性的watcherrender函数的watcher,从而实现自动更新。这一机制提高了性能并确保计算属性的有效运作。

1# 初始化计算属性:initComputed

// 初始化计算属性,遍历组件实例上的computed配置
function initComputed(vm) {
  const computed = vm.$options.computed;
  
  // 创建一个对象用于保存每个计算属性对应的watcher实例
  const watchers = (vm._computedWatchers = {});
  
  // 遍历computed对象
  for (let key in computed) {
    let userDef = computed[key];

    // 判断计算属性的定义方式,可以是函数形式或者包含get方法的对象形式
    let fn = typeof userDef === 'function' ? userDef : userDef.get;

    // 给每个计算属性创建一个Watcher实例,并设置为lazy模式,不会立即执行get函数
    watchers[key] = new Watcher(vm, fn, { lazy: true });

    // 对计算属性进行劫持,即定义getter和setter
    defineComputed(vm, key, userDef);
  }
}

2# 劫持计算属性:defineComputed

// 劫持计算属性,定义getter和setter方法
function defineComputed(target, key, userDef) {
  // 获取计算属性的set方法,如果不存在则默认为空函数
  const setter = userDef.set || (() => {});

  // 使用Object.defineProperty定义计算属性的getter和setter
  Object.defineProperty(target, key, {
    // 定义getter,通过createComputedGetter函数创建
    get: createComputedGetter(key),
    // 设置setter
    set: setter,
  });
}

3# 访问计算属性:createComputedGetter

// 劫持计算属性的访问,返回getter函数
function createComputedGetter(key) {
  return function () {
    // 获取计算属性对应的watcher
    const watcher = this._computedWatchers[key];

    // 如果该计算属性需要重新计算(dirty为true),则执行evaluate方法重新求值
    if (watcher.dirty) {
      // 重新求值后,dirty变为false,下次就不会重新计算,而是返回缓存的值
      watcher.evaluate();
    }

    // 当前计算属性watcher出栈后,如果还有渲染watcher或其他计算属性watcher,
    // 让当前计算属性watcher订阅的dep也收集上一层的watcher(可能是计算属性watcher,也可能是渲染watcher)
    if (Dep.target) {
      watcher.depend();
    }

    // 返回watcher上的值作为计算属性的值
    return watcher.value;
  };
}

4# 计算属性监听器:Watcher

class Watcher {
  constructor(vm, fn, options) {
    this.vm = vm;
    this.lazy = options.lazy; // lazy标识,用于判定是否为懒执行的计算属性watcher
    this.dirty = this.lazy; // dirty标识,用于判定是否需要重新读取get返回值或者使用缓存值

    this.value = this.lazy ? undefined : this.get(); // 存储get返回值
  }

  // 更新方法,用于触发计算属性的重新渲染
  update() {
    console.log('computed-watcher-update');
    if (this.lazy) {
      // 当计算属性依赖的值发生改变时,设置dirty标识为true,会在下次重新读取值时更新计算属性
      this.dirty = true;
    } else {
      queueWatcher(this); // 将当前watcher加入异步渲染队列
      this.get(); // 触发重新渲染
    }
  }

  // 当计算属性watcher为dirty时,执行evaluate,并将dirty标识置为false
  evaluate() {
    console.log('computed-watcher-evaluate');
    this.value = this.get(); // 重新获取用户函数的返回值
    this.dirty = false; // 标识已经重新读取了值
  }

  // 计算属性可能存在嵌套的情况,此时需要计算属性watcher订阅的dep去收集上一层的watcher
  // 该watcher可能是计算属性watcher,也可能是渲染watcher
  depend() {
    let i = this.deps.length;
    while (i--) {
      this.deps[i].depend();
    }
  }
}