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

Posted 在厕所喝茶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2源码-- computed 计算属性的实现相关的知识,希望对你有一定的参考价值。

目录

作用

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

特点

  • 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();
    
  

以上是关于vue2源码-- computed 计算属性的实现的主要内容,如果未能解决你的问题,请参考以下文章

Vue2 computed中的setter

Vue2 computed中的setter

Vue2Computed 计算属性

vue2计算属性computed

vue2.0中的watch和计算属性computed

vue2计算属性(computed)与侦听器(watch)详解