Vue3实现计算属性的代码详解
作者:护国神蛙
代码解释
import { ReactiveEffect } from "./effect" class computedImpl { private _fn: any private stop: boolean = true private _value: any private _effect: any constructor(fn) { this._fn = fn // (1) this._effect = new ReactiveEffect(fn, () => { // (2) if (!this.stop) { this.stop = true } }) } get value() { if(this.stop) { this.stop = false this._value = this._effect.run() } return this._value } } export function computed(fn) { return new computedImpl(fn) }
可以看到代码其实很简单,创建了一个computedImpl类,这个类接收一个函数作为其参数,最下面将实例化的computedImpl对象抛出即可。
看着很简单,那我们来介绍下计算属性在这个类里面做了什么?
一、实例化的时候会将传进去函数缓存起来(1)
二、创建一个ReactiveEffect对象,将传入的fn作为参数传入,第二个参数处接收一个scheduler函数(2)
() => { if (!this.stop) { this.stop = true } }
这里介绍一下这一步的作用:
Vue3 有一个ReactiveEffect这样的类,接收两个参数,一个是默认函数,另一个是scheduler函数。
创建这样的类有什么作用呢?首先我们知道传入的函数里面肯定有响应式的数据,因为计算属性的作用是当响应式数据更新时重新计算我们传入的计算属性函数。
,意味着我们要给给这些响应式数据去收集一个依赖,用于告诉我们计算属性响应式数据是否有发生变动。
那我们Vue3是怎么去收集依赖的呢?那就不得不提到ReactiveEffect了,实例化ReactiveEffect对象有一个run函数,当我们执行run函数的时候会执行一遍我们传入的fn并且将自身指向一个全局变量activeEffect
,当我们响应式数据发生取值操作的时候就会去收集依赖,就是去判断activeEffect
是否存在,如果存在收集起来。
当响应式数据发生set操作的时候,就会去触发依赖,那么我们之前存进去的实例化ReactiveEffect对象就会被拿出来执行,这样我们就可以知道数据发生了改变,默认情况下会执行ReactiveEffect传进的默认函数,但是如果scheduler函数存在那就会执行scheduler函数。
三、计算属性函数执行与缓存
上面已经知道如何实现依赖收集的了,那么讲解下函数执行和缓存的实现。
因为我们已经创建了一个实例化ReactiveEffect对象,在执行这个对象的run函数时会执行我们传入的函数,所以当我们get Value的时候就会执行一遍函数,因为执行函数的时候会,触发响应式对象的取值操作,ReactiveEffect对象被存储到依赖中,并且返回函数的计算结果。
这样我们每次执行get Value都会执行函数拿到函数结果。因为computed会有缓存功能,所以这里又设置了一个_value和stop用在结果缓存和开关,默认get Value操作走一遍stop的判断函数执行完之后关闭开关,防止每次执行都会走一遍函数,然后将函数结果缓存到_value中,下次直接拿_value就行
if(this.stop) { this.stop = false this._value = this._effect.run() } return this._value
上面的代码可以看到我们这个开关一关闭就不会打开了,那我们岂不是每次都会拿到老的值?
回到上面的实例化ReactiveEffect对象中,这里传入了一个scheduler函数。
() => { if (!this.stop) { this.stop = true } }
当响应式数据发生改变的时候scheduler函数执行,stop开关打开,那我们下一次执行get Value就会重新执行函数更新计算属性的结果
总结
在computed实际上是返回了一个实例化的对象,这个对象在实例化的时候接收了一个参数就是computed内部的回调。
因为computed是缓存的,所以返回的实例化对象中有一个get方法,取值的时候就会将传入的回调给执行了,并且把结果设置为实例化对象的内部属性,并返回出去。
所以computed在取值的时候实际上就读取了存在在对象中的回调函数的执行结果。
因为computed会缓存所以在执行get方法的时候会做一个开关,比如说lazy默认为true,只有当lazy为true才能执行回调函数,当函数执行完lazy改成false,所以后续的取值操作都不执行函数,实现了缓存的功能。
因为如果computed里面的响应式对象如果发生了改变,computed的结果是需要更新的,所以需要对computed里面的响应式对象进行一个依赖设置。
vue在进行依赖收集的时候会用一个全局的变量去存储回调函数,在响应式对象get操作的时候就会判断这个全局变量是否存在,如果存在则加到依赖中,后续set操作的时候就拿这些存储的依赖处理执行。
所以computed在实例化对象的时候,会构造一个这样一个全局变量然后里面的回调函数的作用是如果执行lazy置为true,即开关打开。
然后因为去取值的时候会执行一次函数,所以响应式对象get被触发,把lazy置为true的回调存入依赖中,下次响应式对象更新值即set的时候,computed返回的实例对象内部的开关就会被打开了。
所以我们下一次执行computed的value的时候,因为开关是开的所以会重新执行一遍函数并更新实例化对象的值,然后开关关上缓存起来,返回新的值。
以上就是Vue3实现计算属性的代码详解的详细内容,更多关于Vue3计算属性的资料请关注脚本之家其它相关文章!