vue2源码(五)-- computed 计算属性的实现

18 篇文章 1 订阅
14 篇文章 0 订阅

作用

当依赖的响应式数据发生变化时,计算属性会被重新计算。注意如果发生变化的是一个非响应式数据时,计算属性不会被重新计算

特点

  • computed 本质上也是通过Watcher类实现的

  • 客户端具有缓存效果

  • 服务端渲染是没有缓存效果的,是直接通过调用函数获取的结果

原理分析

1、首先每个 vue 实例上面都会有_computedWatchers属性来存储每个计算属性的 watcher 实例

2、Watcher在初始化的时候传入 4 个参数,第一个是vm实例,第二个是getter函数(也就是用户定义计算属性的那个函数),第三个是空函数(没有啥作用),第四个参数是 options 配置项,配置项包含了lazy字段,值为true,用来标识这是一个计算属性的WatcherWatcher在初始化的时候,如果是一个计算属性的Watcher,并不会去立刻调用get函数去读取值(只有真正被读取到的时候才会去触发依赖收集),同时会初始化this.lazy来标识这是一个计算属性的Watcherthis.dirty标识所依赖的数据是否发生改变

3、通过调用createComputedGetter来给计算属性创建一个getter函数。这个getter函数首先根据 key 值获取对应的watcher实例。通过判断dirty字段是否为 true 来重新计算计算属性的值,最终吧value字段返回

4、当用户真正第一次使用这个计算属性的时候,就会计算对应的值,也就是调用了Watcher实例的get函数,从而触发了依赖收集,计算属性的watcher实例会被收集到所依赖的数据的依赖管理器当中。

5、当依赖的数据发生变化时,会通知更新,也就是调用了Watcher实例的update函数,update函数会把dirty值变为true,表示依赖的数据发生了变化。当用户再次读取该计算属性的时候,发现dirty值为true,就会重新计算值,然后再把dirty变为fasle,下次用户再次读取的时候,因为dirtyfalse,不会重新计算,直接返回了上一次计算的值,所以达到了缓存的效果

源码

源码位于src/core/instance/state.js

initComputed函数:

function initComputed(vm: Component, computed: Object) {
  const watchers = (vm._computedWatchers = Object.create(null));
  // 是否服务端渲染
  const isSSR = isServerRendering();
  // 遍历computed每一项
  for (const key in computed) {
    const userDef = computed[key];
    // 如果是函数,则该函数默认是getter;不是函数说明是一个对象,则获取对象上面的get函数
    const getter = typeof userDef === "function" ? userDef : userDef.get;
    if (process.env.NODE_ENV !== "production" && getter == null) {
      // 取值器不存在,报错
      warn(`Getter is missing for computed property "${key}".`, vm);
    }

    if (!isSSR) {
      // 不是服务端渲染的情况下,创建一个watcher实例,并保存到watchers中
      // computed实际上就是通过watcher实现的,第四个参数是关键{ lazy: true }
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      );
    }

    if (!(key in vm)) {
      defineComputed(vm, key, userDef);
    } else if (process.env.NODE_ENV !== "production") {
      // 非生产环境下,检查是否跟props,data,methods中的属性重名了
      // ...
    }
  }
}

defineComputed函数:

function defineComputed(target: any, key: string, userDef: Object | Function) {
  // 非服务端环境才有缓存效果
  const shouldCache = !isServerRendering();
  if (typeof userDef === "function") {
    // 函数的情况下默认函数就是取值器
    sharedPropertyDefinition.get = shouldCache
      ? // 客户端
        createComputedGetter(key)
      : //   服务端渲染
        createGetterInvoker(userDef);
    sharedPropertyDefinition.set = noop;
  } else {
    // 不是函数的情况下,就将它作为对象处理
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop;
    sharedPropertyDefinition.set = userDef.set || noop;
  }
  // ...
  // 将计算属性绑定到this上面
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

createComputedGetter函数:

function createComputedGetter(key) {
  // 返回一个computed的get函数
  return function computedGetter() {
    // 获取对应computed的watcher实例
    const watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        // 如果依赖的数据发生了变化,通过调用watcher的update函数,吧dirty的值变为true,需要重新计算值
        watcher.evaluate();
      }
      if (Dep.target) {
        // 依赖收集
        watcher.depend();
      }
      // 计算出来的值存储在value中
      return watcher.value;
    }
  };
}

createGetterInvoker函数:

function createGetterInvoker(fn) {
  return function computedGetter() {
    return fn.call(this, this);
  };
}

Watcher类,源码位于src/core/observer/watcher.js

export default class Watcher {
  // ...

  constructor(
    vm: Component,
    expOrFn: string | Function
    // ...
  ) {
    this.vm = vm;
    vm._watchers.push(this);
    // options
    if (options) {
      // ...
      // lazy为true表明是一个计算属性
      this.lazy = !!options.lazy;
      // ...
    } else {
      this.lazy = false;
    }
    // 记录计算属性的返回值是否有变化,计算属性的缓存就是通过这个属性来判断的
    // true就是计算属性依赖的数据发生变化
    this.dirty = this.lazy; // for lazy watchers

    // watcher的时候是表达式路径字符串,computed的时候是个函数
    if (typeof expOrFn === "function") {
      this.getter = expOrFn;
    } else {
      // ...
    }
    // 如果是compute先不读取值,等到真正被是用到了在读取
    // 如果是普通的watcher,需要立刻读取值,这样才能让数据收集到依赖,
    // watcher需要立刻读取值是因为,有可能用户只设置值,不读取值,那这样子数据就收集不到依赖,从而无法进行监听数据的改变
    this.value = this.lazy ? undefined : this.get();
  }

  get() {
    // ...
  }

  /**
   * 响应式数据发生变化后,会调用这个函数
   */
  update() {
    if (this.lazy) {
      // 计算属性
      // 标识计算属性依赖的数据发生了变化
      this.dirty = true;
    }
    // ...
  }

  /**
   * 计算属性重新计算值
   */
  evaluate() {
    // 重新计算
    this.value = this.get();
    // 从新计算完成之后,需要把标志位置为true,否则就没有缓存效果了
    this.dirty = false;
  }

  /**
   * Depend on all deps collected by this watcher.
   */
  depend() {
    let i = this.deps.length;
    while (i--) {
      this.deps[i].depend();
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
vue-async-computed.js是一个用于Vue.js的库,它扩展了Vue中的计算属性功能,使之可以支持异步数据获取和计算。 Vue.js计算属性是在模板中根据数据的变化实时计算出新的结果,并实时更新到视图中。然而,在某些情况下,数据的获取和计算可能会涉及异步操作,如从服务器获取数据或处理复杂的计算逻辑。此时,普通的计算属性就不再适用,需要使用异步计算属性才能处理这样的情况。 异步计算属性提供了一个方便的方式来处理异步数据的获取和计算。它的用法类似于普通的计算属性,但是需要使用一个异步函数来定义计算属性的逻辑。这个异步函数可以返回一个Promise对象,用于表示异步操作的结果。 在异步函数中,我们可以执行异步操作,如发送网络请求、读取本地存储等。当异步操作完成后,我们可以通过Promise的resolve方法返回计算属性的结果。在模板中使用异步计算属性时,Vue会自动等待异步操作完成并获取到计算属性的结果,然后更新视图中相应的内容。 通过使用vue-async-computed.js,我们可以轻松地处理复杂的异步计算逻辑,如缓存异步计算结果、处理异步错误等。它的简单易用和强大的功能使得我们可以更加方便地开发处理异步数据的应用程序。无论是获取远程数据还是处理复杂的计算逻辑,vue-async-computed.js都可以帮助我们减少工作量,提高开发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值