Vue 计算属性缓存与方法的区别官方文档说的很清楚,但是其中以Date.now()为例似乎不能太直观的看出他们的差异,因为当我们重复使用方法调用Date.now()时获取的都是相同的值,这看似与计算属性却是相一致。但事实是他们差异还是存在的,我对此进行了一些研究。发现是因为Date.now()的精度问题导致的。

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应依赖关系缓存的。计算属性只在相关响应式依赖发生改变时它们才会重新求值。 ——Vue3官方中文文档

最近在系统看vue3,看到计算属性这里,我就在想,似乎这和直接调用方法是一致的,那么他只是做了语法上的优化吗?

果然后面就告诉我了:是的结果是一样的,但是计算属性是基于响应依赖关系而缓存的。

虽然有些绕口,但是大概也能理解他的意思,即:就是说方法会在每次渲染的时候重新计算,而计算属性如果依赖的量没有变化,那么他会从缓存中调用他的结果,而非重新计算。

紧接着官方又给出了一段例子:

这也同样意味着下面的计算属性将不再更新,因为 Date.now () 不是响应式依赖:

1
2
3
4
5
computed: {
now() {
return Date.now()
}
}

紧接着我又发现问题了,因为按他所说,这里如果我们分别调用一样内容的计算属性和函数渲染在网页中,将得到不同的结果,也就是当我们有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script src="https://unpkg.com/vue@next"></script>
<div id="app" >
<p v-for="i in 3">
Time return by method:{{getTime()}}
</p>
<p v-for="i in 3">
Time return by computed:{{getTimeComputed}}
</p>
</div>
<script>
Vue.createApp({
methods:{
getTime(){
return Date.now();
}
},
computed:{
getTimeComputed(){
return Date.now();
}
}
}).mount('#app')
</script>

你应该会看到使用方法循环三次的内容应该是不同的,而使用计算属性是相同的。
但事实上我的输出结果是
1
2
3
4
5
6
7
8
9
10
11
Time return by method:1629390835396

Time return by method:1629390835396

Time return by method:1629390835396

Time return by computed:1629390835396

Time return by computed:1629390835396

Time return by computed:1629390835396

他们都是一样的,于是我很奇怪,搜寻了很多文章,都没有讲到这个示例的问题。

想来想去,突然想到是否会是时间精度导致的呢?也就是它运行过快导致这个差异被后面的精度给略掉了。

于是把Date.mow()改为了performance.now()这是浏览器api中为了衡量性能的一个更高精度的时间(node中并没有哦)。

和JavaScript中其他可用的时间类函数(比如Date.now)不同的是,window.performance.now()返回的时间戳没有被限制在一毫秒的精确度内,相反,它们以浮点数的形式表示时间,精度最高可达微秒级。

这个时间戳实际上并不是高精度的。为了降低像Spectre这样的安全威胁,各类浏览器对该类型的值做了不同程度上的四舍五入处理。(Firefox从Firefox 59开始四舍五入到2毫秒精度)一些浏览器还可能对这个值作稍微的随机化处理。这个值的精度在未来的版本中可能会再次改善;浏览器开发者还在调查这些时间测定攻击和如何更好的缓解这些攻击。 —— MDN web docs 因此我们更多是借此特性来检测方法的重复运行。

于是我们就得到了

1
2
3
4
5
6
7
8
9
10
11
Time return by method:5518.70000000298

Time return by method:5518.79999999702

Time return by method:5518.89999999851

Time return by computed:5519.20000000298

Time return by computed:5519.20000000298

Time return by computed:5519.20000000298

是的,这正是文档想让我们看到的效果! 我们会看到方法的三次调用所得到结果并非一样的,而computed则全部一致。

当然由于性能关系,有时候你得到的结果也有概率是重复的,但是这比Date.now()要低得多。

注:这里经过在其他浏览器测试,发现部分手机浏览器对精度做了省略,例如夸克甚至直接小数部分只给了一位数,反而获取的也是一样的值,因此这里更推荐用后面的console.log衡量调用次数的差异。

因此我认为文档中这部分使用Date.now()举例并不是很直观,当然也有文章使用console.log()放入函数中,这样我们也可以更直观的看出运行时候的差异。

保持一样的html代码,将js部分修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.createApp({
methods:{
getTime(){
//return Date.now();
console.log("methods~")
return performance.now();
}
},
computed:{
getTimeComputed(){
//return Date.now();
console.log("computed~")
return performance.now();
}
}
}).mount('#app')

我们会得到控制台中输出:
1
2
3
4
"methods~"
"methods~"
"methods~"
"computed~"

这样就很明显了。

你可以在codepen中自己试试: