资讯专栏INFORMATION COLUMN

[Vue.js进阶]从源码角度剖析计算属性的原理

melody_lql / 1508人阅读

摘要:前言最近在学习计算属性的源码,发现和普通的响应式变量内部的实现还有一些不同,特地写了这篇博客,记录下自己学习的成果文中的源码截图只保留核心逻辑完整源码地址可能需要了解一些响应式的原理版本计算属性的概念一般的计算属性值是一个函数,这个函数

前言

最近在学习Vue计算属性的源码,发现和普通的响应式变量内部的实现还有一些不同,特地写了这篇博客,记录下自己学习的成果

文中的源码截图只保留核心逻辑 完整源码地址

可能需要了解一些Vue响应式的原理

Vue 版本:2.5.21

计算属性的概念

一般的计算属性值是一个函数,这个函数会返回一个值,并且其函数内部还可能会依赖别的变量

一般的计算属性看起来和 method 很像,值都是一个函数,那他们有什么区别呢

计算属性和method的区别

将一个计算属性的函数放在 methods 中同样也能达到相同的效果

但是如果视图中依赖了这个 method 的返回值,并且当另外一个其他的响应式变量的修改导致视图被更新时, method 会重新执行一遍,即使这次的更新和 method 中依赖的变量没有任何关系!

而对于计算属性,只有当计算属性依赖的变量改变后,才会重新执行一遍函数,并重新返回一个新的值

点我看示例

当 otherProp 变量被修改导致更新视图的时候,methodFullName 每次都会执行,而 computedFullName 只会在页面初始化的时候执行一次,Vue 推荐开发者将 method 和 compute 属性区分开来,能够有效的提升性能,避免执行一些不必要的代码

回顾过计算属性的概念,接下来我们深入源码,来了解一下计算属性到底是怎么实现的,为什么只有计算属性的依赖项被改变了才会重新求值

从例子入手

这里我写了一个简单的例子,帮助各位理解计算属性的运行原理,下面的解析会围绕这个例子进行解析

const App = {
    template: `
     
{{fullName}}
`
, data() { return { firstName: "尤", lastName: "雨溪", } }, methods: { handleChangeName() { this.lastName = "大大" } }, computed: { fullName() { return this.firstName + this.lastName } } } new Vue({ el: "#app", components: { App }, template: ` ` }).$mount()

fullName 依赖了 firstName 和 lastName,点击 button 会修改 lastName, 同时 fullName 会重新计算,视图变成"尤大大"

深入计算属性的源码

在日常开发中书写的计算属性,实际上内部都会保存一个 watcher, watcher 的作用是观察某个响应式变量的改变然后执行相应的回调,由 Watcher 类实例化而成, Vue 中定义了3个 watcher

render watcher: 模板依赖并且需要显示在视图上变量,其内部保存了一个 render watcher

computed watcher: 计算属性内部保存了一个 computed watcher

user watcher: 使用 watch 属性观察的变量内部保存了一个 user watcher

理解这3个 watcher 各自的作用非常重要,文本会着重围绕 computed watcher 展开

一个计算属性的初始化分为2部分

    实例化一个 computed watcher

    定义计算属性的 getter 函数

生成computed watcher

在初始化当前组件时,会执行 initComputed 方法初始化计算属性,会给每个计算属性实例化一个 computed watcher

在实例化 watcher 时传入不同的配置项就可以生成不同的 watcher 实例 ,当传入{ lazy: true } 时,实例化的 watcher 即为 computed watcher

定义计算属性的 getter 函数

在创建完 computed watcher 后,接着会定义计算属性的 getter 函数,我们在执行计算属性的函数时,实际上执行的是 computedGetter 这个函数

computedGetter代码很少,但是却是计算属性的核心,我们一步步来分析

dirty属性

通过 key 获取到第一步中定义的 computed watcher,随后会判断这个 computed watcher 的 dirty 属性是否为 true,当 dirty 为 true 时, 会执行 evaluate 方法, evaluate 内部会执行计算属性的函数,并且将 watcher 的 value 属性等于函数执行后的结果也就是最终计算出来的值,具体我们放到后面讲

dirty 属性是一个用来检测当前的 computed watcher是否需要重新执行的一个标志,这也是计算属性和普通method的区别,结合上图可以发现,当 dirty 为 false 时,就不会去执行 evaluate 也就不会执行计算属性的函数,可以看到最后直接就返回了 watcher.value 表示这次不会进行计算,会直接使用以前的 value 的值

当第一次触发computedGetter 时,dirty 属性的默认值是 true ,那是因为在初始化 computed watcher时候 Vue 将 dirty 属性等于了 lazy 属性,即为 true

知道 dirty 的默认值为 true,什么时候为 false 呢?我们接着来看 evalutate 具体的实现

evaluate方法

evaluate 方法是 computed watcher 独有的方法,代码也只有短短2行

get方法

第一行执行了 get 方法, get 方法是所有 watcher 用来求值的通用方法

get 主要就做了这三步

    将当前这个 watcher 作为栈顶的 watcher 推入栈

    执行getter方法

    将这个 watcher 弹出栈

我们知道 Vue.js 会维护一个全局的栈用来存放 watcher ,每当触发响应式变量内部的 getter 时,就会收集这个全局的栈的顶部的 watcher(即Dep.target),将这个 watcher 存入响应式变量内部保存的dep中

第一步通过 pushTarget 将当前的 computed watcher 推入全局的栈中,此时Dep.target就指向这个栈顶的 computed watcher

第二步执行 getter 方法, 对于 computed watcher,getter 方法就是计算属性的函数,执行函数将返回的值赋值给 value 属性,而当计算属性的函数执行时,如果内部含有其他的响应式变量,会触发它们内部的 getter ,将第一步放入作为当前栈顶的 computed watcher 存入响应式变量内部的dep对象中

注意响应式变量内部的 getter 和 getter 方法不是一个函数

第三步将这个 computed watcher 弹出全局的栈

之所以将这个 computed watcher 推入又弹出,是为了让第二步执行内部的 getter 时,能让计算属性函数内部依赖的响应式变量收集到这个 computed watcher

对于计算属性来说,get 方法的作用就是进行求值

将dirty设为false

执行完 get 方法,即一旦计算属性执行过一次求值,就会将 dirty 属性设为 false,如果下次又触发了这个计算属性的 getter 会直接跳过求值阶段

结合

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/7324.html

相关文章

  • 关于Vue2一些值得推荐文章 -- 五、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    sutaking 评论0 收藏0
  • 关于Vue2一些值得推荐文章 -- 五、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    khs1994 评论0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件开发前端掘金作者原文地址译者插件是为应用添加全局功能的一种强大而且简单的方式。提供了与使用掌控异步前端掘金教你使用在行代码内优雅的实现文件分片断点续传。 Vue.js 插件开发 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins译者:jeneser Vue.js插件是为应用添加全局功能的一种强大而且简单的方式。插....

    izhuhaodev 评论0 收藏0
  • javascript知识点

    摘要:模块化是随着前端技术的发展,前端代码爆炸式增长后,工程化所采取的必然措施。目前模块化的思想分为和。特别指出,事件不等同于异步,回调也不等同于异步。将会讨论安全的类型检测惰性载入函数冻结对象定时器等话题。 Vue.js 前后端同构方案之准备篇——代码优化 目前 Vue.js 的火爆不亚于当初的 React,本人对写代码有洁癖,代码也是艺术。此篇是准备篇,工欲善其事,必先利其器。我们先在代...

    Karrdy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<