目录
- 1,简单对比
- 2,原理的不同
- 1,method 的处理
- 2,computed 的处理
- 实现缓存
- 触发更新
- 3,触发更新时的问题
1,简单对比
- computed 当做属性使用,method 当做方法使用。
- computed 可以提供 getter 和 setter 来赋值。
- computed 无法接收多个参数。
- computed 有缓存。
2,原理的不同
1,method 的处理
处理比较简单,只需要遍历 methods 配置的每个属性,将其对应的函数通过 bind 绑定到当前实例。之后复制其引用到组件实例即可(为了通过 this 可访问)。
// 伪代码
function Vue(options) {
// ...
// 其他代码
// ...
Object.entries(options.methods).forEach(([methodName, fn]) => {
this[methodName] = fn.bind(this);
});
}
new Vue(vnode.componentOptions);
2,computed 的处理
在触发组件的生命周期函数 beforeCreate
后,会做一系列的事情,其中包括对 computed
的处理:
- 遍历 computed 配置中的所有属性,对每个属性都创建一个
Watcher
对象,并传入一个函数。该函数本质上就是 computed 的 getter。之后 getter 触发时就会收集依赖。
数据响应式中 Watcher 原理参考。
简单来说,为了监听响应式数据的变化来触发更新,Vue 会将使用了响应式数据的函数(模板对应render
函数)通过Watcher
记录下来(收集依赖)。之后响应式数据发生变化,就会通知对应的Watcher
来执行对应的函数触发更新。
-
Watcher
创建好之后,vue 会使用代理模式,将计算属性挂载到组件实例上(为了通过 this 访问)。 -
不同于渲染函数
render
的Watcher
,为计算属性创建的Watcher
不会立即执行。因为要考虑该计算属性是否被使用(渲染函数或其他方法中),没使用就不会执行。所以在创建Watcher
时配置了一个lazy
属性,lazy === true
时Watcher
不会立即执行。
实现缓存
-
受到
lazy
的影响,Watcher
内部还会配置2个属性:value
和dirty
- value,保存
Watcher
运行时的结果,一开始(Watcher
还没有执行时)为undefined
。 - dirty,标记当前
value
是否过期,一开始为true
。
- value,保存
-
当读取计算属性时,会先检查
dirty
,- 为
true
时则运行之前传入Watcher
的函数(也就是计算属性的 getter),将计算依赖得到的值保存在Watcher
的value
中,同时设置dirty = false
。 - 为
false
,则直接返回Watcher.value
,也就是缓存的值。
- 为
触发更新
-
为了实现更新,在收集依赖时,被依赖的数据不仅会收集计算属性的
Watcher
,还会收集组件的Watcher
。 -
当计算属性变化时,会先触发计算属性的
Watcher
,但只会将dirty = true
,其他不做处理。之后触发组件的Watcher
重新渲染。render
函数(或模板)又读取了计算属性,因为dirty === true
,所以会重新运行 getter 计算。 -
设置计算属性时比较简单,直接运行
setter
即可。
3,触发更新时的问题
注意到上面触发更新时,如果计算属性的依赖发生变化,但计算属性没有在模板(或render
函数)中使用时(其他方法或watch场景同理),虽然会触发计算属性的 Watcher
,但不会触发同时收集到的组件的 Watcher
。
举例:
<template>
<div>
<h1 v-if="showName">{{ fullName }}</h1>
<button @click="handleClick">隐藏 fullName</button>
</div>
</template>
<script>
export default {
data() {
return {
firstName: '渣渣',
lastName: '辉',
showName: true
}
},
computed: {
fullName: {
get() {
return this.firstName + ' ' + this.lastName
},
set(newValue) {
;[this.firstName, this.lastName] = newValue.split(' ')
}
}
},
methods: {
handleClick() {
this.firstName = '123' // 计算属性依赖发生变化
this.showName = !this.showName
}
},
updated() {
console.log(this)
}
}
</script>
showName === false
所以不渲染 h1
,所以render
函数不会读取计算属性 fullName
。
所以只会执行:
计算属性的依赖:
firstName: [计算属性的 Watcher,组件的 Watcher(对应render函数 )]
lastName: [计算属性的 Watcher,组件的 Watcher(对应render函数)]
showName: [组件的 Watcher]
以上。