vue2源码-- computed 计算属性的实现
Posted 在厕所喝茶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2源码-- computed 计算属性的实现相关的知识,希望对你有一定的参考价值。
目录
作用
当依赖的响应式数据发生变化时,计算属性会被重新计算。注意如果发生变化的是一个非响应式数据时,计算属性不会被重新计算
特点
-
computed 本质上也是通过
Watcher
类实现的 -
客户端具有缓存效果
-
服务端渲染是没有缓存效果的,是直接通过调用函数获取的结果
原理分析
1、首先每个 vue 实例上面都会有_computedWatchers
属性来存储每个计算属性的 watcher 实例
2、Watcher
在初始化的时候传入 4 个参数,第一个是vm
实例,第二个是getter
函数(也就是用户定义计算属性的那个函数),第三个是空函数(没有啥作用),第四个参数是 options 配置项,配置项包含了lazy
字段,值为true
,用来标识这是一个计算属性的Watcher
。Watcher
在初始化的时候,如果是一个计算属性的Watcher
,并不会去立刻调用get
函数去读取值(只有真正被读取到的时候才会去触发依赖收集),同时会初始化this.lazy
来标识这是一个计算属性的Watcher
,this.dirty
标识所依赖的数据是否发生改变
3、通过调用createComputedGetter
来给计算属性创建一个getter
函数。这个getter
函数首先根据 key 值获取对应的watcher
实例。通过判断dirty
字段是否为 true 来重新计算计算属性的值,最终吧value
字段返回
4、当用户真正第一次使用这个计算属性的时候,就会计算对应的值,也就是调用了Watcher
实例的get
函数,从而触发了依赖收集,计算属性的watcher
实例会被收集到所依赖的数据的依赖管理器当中。
5、当依赖的数据发生变化时,会通知更新,也就是调用了Watcher
实例的update
函数,update
函数会把dirty
值变为true
,表示依赖的数据发生了变化。当用户再次读取该计算属性的时候,发现dirty
值为true
,就会重新计算值,然后再把dirty
变为fasle
,下次用户再次读取的时候,因为dirty
为false
,不会重新计算,直接返回了上一次计算的值,所以达到了缓存的效果
源码
源码位于src/core/instance/state.js
initComputed
函数:
function initComputed(vm: Component, computed: Object)
const watchers = (vm._computedWatchers = Object.create(null));
// 是否服务端渲染
const isSSR = isServerRendering();
// 遍历computed每一项
for (const key in computed)
const userDef = computed[key];
// 如果是函数,则该函数默认是getter;不是函数说明是一个对象,则获取对象上面的get函数
const getter = typeof userDef === "function" ? userDef : userDef.get;
if (process.env.NODE_ENV !== "production" && getter == null)
// 取值器不存在,报错
warn(`Getter is missing for computed property "$key".`, vm);
if (!isSSR)
// 不是服务端渲染的情况下,创建一个watcher实例,并保存到watchers中
// computed实际上就是通过watcher实现的,第四个参数是关键 lazy: true
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
if (!(key in vm))
defineComputed(vm, key, userDef);
else if (process.env.NODE_ENV !== "production")
// 非生产环境下,检查是否跟props,data,methods中的属性重名了
// ...
defineComputed
函数:
function defineComputed(target: any, key: string, userDef: Object | Function)
// 非服务端环境才有缓存效果
const shouldCache = !isServerRendering();
if (typeof userDef === "function")
// 函数的情况下默认函数就是取值器
sharedPropertyDefinition.get = shouldCache
? // 客户端
createComputedGetter(key)
: // 服务端渲染
createGetterInvoker(userDef);
sharedPropertyDefinition.set = noop;
else
// 不是函数的情况下,就将它作为对象处理
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop;
sharedPropertyDefinition.set = userDef.set || noop;
// ...
// 将计算属性绑定到this上面
Object.defineProperty(target, key, sharedPropertyDefinition);
createComputedGetter
函数:
function createComputedGetter(key)
// 返回一个computed的get函数
return function computedGetter()
// 获取对应computed的watcher实例
const watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher)
if (watcher.dirty)
// 如果依赖的数据发生了变化,通过调用watcher的update函数,吧dirty的值变为true,需要重新计算值
watcher.evaluate();
if (Dep.target)
// 依赖收集
watcher.depend();
// 计算出来的值存储在value中
return watcher.value;
;
createGetterInvoker
函数:
function createGetterInvoker(fn)
return function computedGetter()
return fn.call(this, this);
;
Watcher
类,源码位于src/core/observer/watcher.js
export default class Watcher
// ...
constructor(
vm: Component,
expOrFn: string | Function
// ...
)
this.vm = vm;
vm._watchers.push(this);
// options
if (options)
// ...
// lazy为true表明是一个计算属性
this.lazy = !!options.lazy;
// ...
else
this.lazy = false;
// 记录计算属性的返回值是否有变化,计算属性的缓存就是通过这个属性来判断的
// true就是计算属性依赖的数据发生变化
this.dirty = this.lazy; // for lazy watchers
// watcher的时候是表达式路径字符串,computed的时候是个函数
if (typeof expOrFn === "function")
this.getter = expOrFn;
else
// ...
// 如果是compute先不读取值,等到真正被是用到了在读取
// 如果是普通的watcher,需要立刻读取值,这样才能让数据收集到依赖,
// watcher需要立刻读取值是因为,有可能用户只设置值,不读取值,那这样子数据就收集不到依赖,从而无法进行监听数据的改变
this.value = this.lazy ? undefined : this.get();
get()
// ...
/**
* 响应式数据发生变化后,会调用这个函数
*/
update()
if (this.lazy)
// 计算属性
// 标识计算属性依赖的数据发生了变化
this.dirty = true;
// ...
/**
* 计算属性重新计算值
*/
evaluate()
// 重新计算
this.value = this.get();
// 从新计算完成之后,需要把标志位置为true,否则就没有缓存效果了
this.dirty = false;
/**
* Depend on all deps collected by this watcher.
*/
depend()
let i = this.deps.length;
while (i--)
this.deps[i].depend();
以上是关于vue2源码-- computed 计算属性的实现的主要内容,如果未能解决你的问题,请参考以下文章