简介
大家好,我是六六。在我们开发当中,当然少不了使用computed计算属性,都知道结果会被缓存起来,那是怎么做到的呢。当然了,在面试中也经常会问到与watch到底有什么不同,那儿接下来,我们将走进computed的内部,详细的去了解一下。
目录
你知道的computed特性
computed原理是什么
computed原理具体实现
详说整个computed过程
面试题与扩展
总结
1.你知道的computed特性:
计算属性在使用的时候,要当做普通属性使用就好,不需要加()
只要计算属性这个function内部所用到的data中的数据发生了变化,就会立即重新计算这个计算属性的值
计算属性的求值结果,会被缓存起来,方便下次继续使用;如果计算属性方法中,所依赖的任何数据,都没有发生过变化,则不会重新对计算属性求值
可以为函数或者对象
2.computed原理是什么:
学习中最常见听到的一句话就是,computed就是一个特殊的getter方法。在代理函数可以结合watcher实现缓存与收集依赖。
计算属性具有缓存性,如何知道计算属性的返回值发生变化呢?
这其实就是结合了watcher的dirty属性来分辨的,当dirty为true时,说明需要重新计算,当为false时,计算属性没有改变,不需要重新计算,直接读取缓存值就好。
模拟一下计算属性内容发生改变后:
计算属性的watcher和组件内的watcher都会得到通知
计算属性的watcher将自己的属性dirty设置为true
下次读取计算属性时,因为dirty为true重新计算一次值
组件watcher得到通知,从而执行render函数进行重新渲染
3.computed原理具体实现:
3.1 initComputed初始化
const computedWatcherOptions={lazy:true}
function initComputed(vm, computed) {
const watchers = vm._computedWatchers = Object.create(null)
const isSSR = isServerRendering()
for (const key in computed) {
// userDef我们定义的计算属性
const userDef = computed[key]
// 获取getter
const getter = typeof userDef === 'function' ? userDef : userDef.get
// 非SSR环境
if (!isSSR) {
// 创建watcher实例
watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
}
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
复制代码
具体讲解:
initComputed函数接受两个参数:vm实例和computed对象
声明了一个变量watchers,并保存在vm._computedWatchers
声明变量isSSR用于判断是否在SSR环境
使用for..in循环computed对象,以此初始化每个计算属性
用getter保存计算结果的值
创建watcher实例
判断计算属性的值是否已经在vm实例上定义了,如果没有,执行defineComputed函数
到这里,我们就明白了,最后我们会执行defineComputed函数,对每个计算属性进行定义和初始化。下面我们来看看这个函数。
3.2 defineComputed定义计算属性
const sharePropertyDefinition={
enumerable:true,
configurable:true,
get:noop,
set:noop
}
function defineComputed(target,key,userDef){
// 判断环境
const shouldCache=!isServerRendering()
// 设置的为函数
if(typeof userDef==='function'){
sharePropertyDefinition.get=shouldCache?createComputedGetter(key):userDef
sharePropertyDefinition.set=noop
}
// 设置的为对象
else{
sharePropertyDefinition.get=userDef.get?shouldCache&&userDef.cache!=false?createComputedGetter(key):userDef.get:noop
sharePropertyDefinition.set=userDef.set?userDef.set:noop
}
Object.defineProperty(target,key,sharePropertyDefinition)
}
复制代码
具体讲解:
首先定义sharePropertyDefinition变量,用于配合Object.defineProperty使用。
函数defineComputed接受target,key,userDef三个参数
使用shouldCache变量确认是否在SSR环境
判断userDef是否为函数,因为我们知道我们传入的computed支持函数或者对象
如果是函数,判断shouldCache,为true时执行createComputedGetter函数,并赋值给 sharePropertyDefinition.get
如果不是函数,是否写了get函数,判断shouldCache,为true时执行createComputedGetter函数,并赋值给 sharePropertyDefinition.get
在判断用户是否写了set函数,写了就赋值给sharePropertyDefinition,noop为空函数。
在最后,执行Object.defineProperty向实例挂载属性
所以,计算属性的缓存和响应式主要在于是否将getter方法设置为createComputedGetter。因为最终挂载到get方法的就是createComputedGetter函数。
3.3createComputedGetter缓存与响应式的关键
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
复制代码
具体讲解:
在initComputed函数中我们定义了_computedWatchers属性,通过_computedWatchers[key]拿到我们定义的watcher,所以变量watcher就是每个watcher的实例
判断watcher.dirty值是否为真,为真就重新计算一下,也就是执行watcher.evaluate()
判断Dep.target的值,如果有,就执行 watcher.depend(),最后返回watcher的value值。
代码是非常简单的,但是理解起来是需要配合watcher和dep的,如果不了解可以参考我这篇文章:
深入浅出Vue变化侦测
3.4 与watcher密不可分
class Watcher{
constructor(vm,expOrFn,cb,options){
// 无关代码
if(options){
this.lazy=!!options.lazy
}
this.dirty = this.lazy
}
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
}
复制代码
evaluate方法:就是从watcher重新获取一次表达式的值
depend方法:
this.deps[i]就是计算属性所依赖的状态
调用depend方法可以将组件的watcher实例添加到dep实例中,意思就是计算属性所依赖的状态改变也会通知组件,更具体说,就是组件watcher也会观察计算属性所依赖的状态。(组件watcher是如何拿到的呢)
4.详说整个computed过程:
使用watcher读取计算属性
读取计算属性函数中的数据,定义响应式时,get读取的就是watcher.value
计算属性和组件watcher同时观察数据的变化
当数据改变后,计算属性和组件watcher都会收到通知
组件watcher会重新渲染组件
计算属性watcher因为数据改变,dirty属性为true,将重新计算
计算属性计算的结果用于本次渲染,并缓存起来
5.面试题:watch和computed的区别是什么?
其实我觉得这两个作用是完全不一样,不知道为什么总拿来比较。
watch是一种行为,在状态改变之后需要做什么。
computed就是一种状态,也可以说多种状态初始化后的结果。
我认为把,computed与filter作为比较不是更好一些吗?都是用来初始化状态用的。
computed更适用于大量数据计算的结果,并且反复使用,而且不常更新。因为有缓存,大大提升性能。
filter适用于少量数据进行初始化处理,计算量不能太大,因为每次渲染都会计算,并且可以频繁更新。
6.总结
一开始学这个知识点确实很懵,一个知识点看了很多遍,到最后才领悟过来,不过也有很多一知半解的地方,所以每天还是要继续努力。
关于找一找教程网
本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。
本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。
[Vue深入浅出computed(进阶必看系列)]http://www.zyiz.net/tech/detail-131176.html