说说Vue的几个watcher(二)——computed watcher__Vue.js
发布于 3 年前 作者 banyungong 1148 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

为降低心智负担,本文只做原理和流程的分析,尽量不涉及Vue.js源码。
如果想要看结合源码的分析,建议猛戳这里:《Vue 的计算属性真的会缓存吗?(保姆级教学,原理深入揭秘)》。这篇是我看过的最好的Vue.js原理分析文章,没有之一。
作者写的所有Vue.js原理解析文章都非常好,建议大家都能阅读一下并关注作者(与我利益无关,与大家利益相关啊)

watcher系列文章在此,本篇是第二篇

使用场景

computed属性值一般可以用在哪些地方呢?我们说两个常用简单例子

  • 示例1:模板里

    export default {
      template: '<div>My name is {{ fullname }}</div>',
      data () {
        return {
          firstName: 'Steve',
          lastName: 'Jobs'
        }
      },
      computed: {
        fullName: ({ firstName, lastName }) => `${firstName}·${lastName}`
      }
    }
    
  • 示例2:模板、以及其他计算属性里

    export default {
      template: '<div>{{ selfIntroduction }}</div>',
      data () {
        return {
          firstName: 'Steve',
          lastName: 'Jobs'
        }
      },
      computed: {
        fullName: ({ firstName, lastName }) => `${firstName}·${lastName}`,
        selfIntroduction: ({ fullName }) => `My name is ${fullName}`
      }
    }
    

建立联系

看过了上面的使用场景,我们应该提出的问题是:

  • data属性值变了,computed属性怎么知道?
  • computed的属性值变了,模板渲染函数怎么知道?

经过了上节render-watcher的介绍,我们应该有了一个大概的思路:

  • 得为computed属性创建watcher(正是computed-watcher),并添加到相关data属性值的订阅者队列里。watcher的回调函数正是computed属性值(函数)
  • computed属性值的变化是由data属性变化引起的。因此若想模板也跟着变化,render-watcher就得添加到computed属性的相关data的订阅者队列里。
    有点绕?对照上面代码示例1来看,就是render-watcher得添加到firstNamelastName的订阅者队列里

下面我们来看看怎么建立这个联系

依赖收集

computed属性的初始化

首先要对computed属性进行初始化。

当完成所有data属性值的访问劫持劫持之后,便进入了computed属性的初始化流程:

  • 遍历computed属性,为每个computed属性都创建一个watcher;
  • 遍历computed属性,对每个computed属性进行get劫持

其中get劫持是最关键的步骤。在这个环节里,实现了我们上面提出的解决方案:

  • 将computed-watcher添加到data属性值的订阅者队列。
  • 将render-watcher添加到data属性值的订阅者队列。

这样就能实现,当data属性值变化后:

  • render函数执行
  • computed属性函数执行并返回新执行结果

我们来看看具体是怎么实现的:

computed属性的get劫持

齿轮从render函数被执行开始转起。

render函数执行时,触发了computed属性值的get。

接着,在get里依序发生了这么几件事:

  • 执行computed属性函数,将函数返回值保存起来

    注意,执行computed函数的过程中,data属性值被get了。

  • data属性值把当前的computed属性的watcher添加到订阅队列里

  • data属性值把render-watcher添加到订阅队列里

  • 返回保存起来的computed函数执行结果

注意订阅队列里的顺序,computed-watcher先,render-watcher后。

好了,齿轮的第一次运转结束了。

连携反应

模板直接引用computed属性

data的属性值被改变了。比如我们修改示例1中的lastName属性:this.lastName = 'Nash'

好了,这个时候会发生什么?

没错,齿轮又一次运转了起来。

  • data的属性值被set了
  • data属性值通知所有订阅者,你们要更新了
  • computed-watcher执行computed属性函数,返回了新的值
  • render-watcher执行render回调函数,渲染了新的视图

逻辑好的读者可能已经开始起疑了:

computed-watcher回调执行了一次computed函数,render-watcher回调render函数,会触发computed属性的get进而又执行了computed函数,这不是重复执行了两次?

说的对!这么干不行!我们只能挑一个watcher,在其回调里触发computed函数的执行。这么做:

  • 在computed-watcher的回调中,我们将computed属性标记为“待更新“
  • 在render-watcher→render函数→computed属性值被get时,看有没有”待更新“标记,有就执行computed函数,执行完了就取消掉“待更新”标记。没有就直接返回上次执行后保存下来的结果

抽出上面的核心逻辑,用伪代码表示一下:

// dirty就是数据该更新了
if (dirty) {
	setComputedValueCache(update())
	dirty = false
}
return getComputedValueCache()

一言蔽之,每次都返回缓存值。无非是返回前更不更新这个缓存值而已。

到这里,我们已经弄清楚,在模板里使用computed属性值时,是如何做到响应式的了;也弄清楚computed-watcher是什么、干什么用、怎么用了。

事已至此,顺手梳理一下上面示例2的情况,即:当computed属性引用了computed属性时,会发生什么?

模板间接引用computed属性

什么是间接引用computed属性?看上面示例2就知道,引用链如下:

模板 → computed属性1 → computed属性2 → data属性值

computed属性2就是模板的间接引用。

经过上文,想必我们已经不必再从头开始分析了(肝不动了😂😂😂)。直接看data属性值最后的订阅队列:

data属性值→ [computed-watcher1, computed-watcher2, render-watcher]。

data属性值被set的时候,computed-watcher1、computed-watcher2相继被标记“待更新”;待到render-watcher更新时,就会依序执行:

  • render函数
  • computed1属性被get
  • computed1属性函数执行
  • computed2属性被get
  • computed2属性函数执行
  • 缓存并返回computed2结果给computed1
  • 缓存并返回computed1结果给render函数
  • render函数将computed1新值渲染到视图中

至此,我们就可以对computed-watcher有了最基本、最简单的认识了。

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 晒了个太阳 原文链接:https://juejin.im/post/6844904128435453966

回到顶部