Computed缓存原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Computed缓存原理相关的知识,希望对你有一定的参考价值。

参考技术A  computed的计算属性有缓存机制,只有当其依赖的响应式数据发生变化时才会清空缓存重新计算结果,其缓存机制本质是通过一个dirty属性控制的,只有dirty为true时才会重新计算结果替换缓存。dirty只有当其响应式数据发送变化时才会设置为true,重新计算后会再次被设置为false

      假设一个template中 computedVal // computedVal定义在计算属性中,是a+b

    另一个是  a+b 的 moustache 语法中是一个计算属性,

    那么每次render的时候都会计算a+b,而computedVal不会计算,可以直接从computedVal中拿值(对应的一个watcher的value)

<template>

      <div>

          <button @click="changeValue">更新Value</button>

          <button @click="getComputedValue">打印computedValue</button>

     </div>

</template>

<script>

    export default

              data()

                     return

                              value: 1

                   

          ,

       computed:

              computedValue()

                       return this.value + '--' + Math.random()

             

        ,

      methods:

              changeValue()

                        this.value++;

            ,

          getComputedValue()

                  console.log(this.computedValue);

           

     



</script>

      点击第二个按钮,多次获取computedValue的值时,返回的值都是相同的,Math.random()不会重新获取。体现了computed的缓存特性。只有当点击了第一个按钮,修改了computedValue依赖的响应式数据后,才会更新computedValue的缓存

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

以上是关于Computed缓存原理的主要内容,如果未能解决你的问题,请参考以下文章

vue2中computed原理

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

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

vue3中的computed和watch

Vue computed和watch

25、watch 和computed区别 以及computed的缓存