vuecomputed缓存机制有什么用,vue中的缓存
本文主要介绍vue computed的缓存实现原理,帮助大家更好的理解和学习使用vue。感兴趣的朋友可以了解一下。
目录
初始化computed依赖关系集合,分发更新摘要本文重点通过下面的例子来解释computed的初始化和更新的过程,看看如何缓存计算属性,如何收集依赖关系。
div id=应用程序
span @ click= change { sum } }/span
/div
脚本src=。/vue 2.6 . js /脚本
脚本
新Vue({
埃尔: #app ,
data() {
返回{
计数:1,
}
},
方法:{
更改(){
this.count=2
},
},
计算值:{
sum() {
把这个还回去。第一次
},
},
})
/脚本
初始化 computed
vue初始化时,首先执行init方法,里面的initState会初始化计算出来的属性。
if(opts . computed){ init computed(VM,opts . computed);}
下面是initComputed的代码。
var watchers=vm。_ computed watchers=object . create(null);
//依次为每个计算属性定义一个计算观察器
for(计算中的常量键){
const userDef=computed[key]
watchers[key]=新观察者(
虚拟机//实例
Getter,//用户传入的评估函数sum
Noop,//回调函数可以先忽略。
{lazy: true} //声明lazy属性标记计算观察器
)
//当用户调用this.sum时会发生什么
定义计算的(虚拟机、密钥、用户定义)
}
每个计算属性对应的计算观察器的初始状态如下:
{
部门:[],
脏:真的,
getter:sum(),
懒:真的,
值:未定义
}
你可以看到它的值一开始是未定义的,lazy是真的,也就是说它的值是懒惰计算的,只有在模板中读取它的值后才会计算。
这个脏属性实际上是缓存的关键。先记住。
接下来,我们来看看更关键的defineComputed,它决定了用户读取this.sum(一个计算属性)的值后会发生什么。继续简化,排除一些不影响流程的逻辑。
Object.defineProperty(目标,键,{
get() {
//从刚才提到的组件实例中获取计算的观察器
const watcher=这个。_computedWatchers this。_computedWatchers[key]
如果(观察者){
//只有脏的才会被重新计算。
if (watcher.dirty) {
//将在此处进行计算,调用get,并设置Dep.target
watcher.evaluate()
}
//这也是一个重点。我们以后再谈。
if (Dep.target) {
watcher.depend()
}
//最后返回计算出的值
返回观察器.值
}
}
})
这个函数需要仔细研究。它做了几件事。我们用初始化过程来解释一下:
首先,dirty的概念代表脏数据,也就是说这个数据需要通过再次调用用户传入的sum函数进行求值。先抛开更新时的逻辑。当第一次在模板中读取{{sum}}时,它必须为true,因此初始化将进行评估。
evaluate () {
//调用get函数进行求值
this.value=this.get()
//将dirty标记为false
this.dirty=false
}
这个功能其实很明确。它首先计算,然后将dirty设置为false。回头看看刚才我们的Object.defineProperty的逻辑。下次在没有特殊情况下读sum,发现dirty是假的。仅仅返回值watcher.value就够了吗?这其实就是计算属性缓存的概念。
依赖收集
初始化后会调用render进行渲染,render函数作为观察器的getter,此时的观察器就是渲染观察器。
updateComponent=()={
vm。_更新(虚拟机。_render(),补水)
}
//创建呈现观察器。渲染观察器初始化时,会调用它的get()方法,即render函数,并进行依赖收集。
新观察器(vm,updateComponent,noop,{},true /* isRenderWatcher */)
看看watcher中的get方法。
get () {
//将当前的watcher放在栈顶,同时设置为Dep.target
推送目标(this)
传能线密度值
const vm=this.vm
//调用用户自定义函数时,会访问this.count,从而访问它的getter方法,下面会讨论。
value=this.getter.call(vm,vm)
//求值结束后,当前观察器退出堆栈。
popTarget()
this.cleanupDeps()
返回值
}
渲染监视器的getter执行时(render函数),会访问this.sum,会触发计算属性的getter,也就是初始化计算监视器时定义的方法。得到绑定到sum的计算观察器后,由于初始化时dirty为true,将调用其evaluate方法,最后调用其get()方法,将计算观察器放在栈顶。此时,Dep.target还将监视计算的监视器。
然后调用它的get方法,就会访问this.count,触发count属性的getter(如下图),将当前Dep.target中存储的watcher收集到count属性对应的Dep中。此时,计算完成,调用popTarget()将观察器推出堆栈。此时,最后一个渲染监视器位于堆栈顶部,Dep.target再次成为渲染监视器。
//在闭包中,将保留为键计数定义的dep。
const dep=新dep()
//最后一个set函数设置的val也会保留在闭包里
让瓦尔
Object.defineProperty(obj,key,{
get:函数reactiveGetter () {
常数值=val
//Dep.target此时计算watcher。
if (Dep.target) {
//收集依赖项
dep.depend赖()
}
返回值
},
})
//dep.depend()
依赖(){
if (Dep.target) {
Dep.target.addDep(这)
}
}
//观察器的addDep函数
addDep (dep: Dep) {
//这里进行了一系列重复数据消除操作来简化它。
//在这里,count的dep也会存储在自己的dep中
this.deps.push
//再次将观察器本身作为参数
//回到dep的addSub函数
dep.addSub(这)
}
类Dep
subs=[]
addSub (sub: Watcher) {
这个. subs.push(子)
}
}
通过这两段代码,计算观察器由属性绑定的dep收集。观察者依赖于dep,dep也依赖于观察者。它们之间这种相互依赖的数据结构可以很容易地知道一个观察器依赖于哪个Dep,以及一个Dep依赖于哪个观察器。
然后执行watcher.depend()
//watcher.depend
依赖(){
设i=this.deps.length
while (i - ) {
这个deps[i]。依赖()
}
}
还记得刚才计算守望者的形态吗?计数的deps保存在其deps中。也就是说,将再次调用dep.depend () on count。
类Dep
subs=[]
依赖(){
if (Dep.target) {
Dep.target.addDep(这)
}
}
}
这次的Dep.target已经是一个渲染监视器,因此这个计数的Dep将把渲染监视器存储在它自己的subs中。
收集count的最终依赖项,其dep为:
{
Subs: [sum的sum计算观察器,呈现观察器]
}
派发更新
然后我们就到了这个问题的关键点。此时,计数被更新。我们是如何触发视图更新的?
回到count的响应式劫持逻辑:
//在闭包中,将保留为键计数定义的dep。
const dep=新dep()
//最后一个set函数设置的val也会保留在闭包里
让瓦尔
Object.defineProperty(obj,key,{
set:函数reactiveSetter (newVal) {
val=newVal
//通知触发计数的dep
dep.notify()
}
})
})
好了,这就触发了我们刚才精心准备的dep of count的notify函数。
类Dep
subs=[]
通知(){
for(设i=0,l=subs.lengthI l;i ) {
分句[i]。更新()
}
}
}
这里的逻辑很简单。存储在subs中的观察器依次调用它们的更新方法,即
呼叫观察者的更新
调用渲染监视器的更新
计算观察者的更新
update () {
如果(this.lazy) {
this.dirty=true
}
}
只需将计算观察器的dirty属性设置为true,静静等待下一次读取(再次执行render函数时,将再次访问sum属性,当dirty为true时,将再次求值)。
渲染观察者的更新
其实这里就是调用函数vm。_更新(虚拟机。_render())根据render函数生成的vnode再次渲染视图。
而在渲染的过程中,su的值会被访问,所以又回到了由sum定义的get:
Object.defineProperty(目标,键,{
get() {
const watcher=这个。_computedWatchers this。_computedWatchers[key]
如果(观察者){
//dirty在上一步中已设置为true,因此将重新计算它
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
//最后返回计算出的值
返回观察器.值
}
}
})
由于上一步中的响应属性更新,触发计算观察器的脏更新为真。所以会再次调用用户传入的sum函数来计算最新值,页面上自然会显示最新值。
至此,整个计算属性更新的过程结束。
总结一下
初始化data和computed,分别代理它们的set和get方法,并为数据中的所有属性生成唯一的dep实例。
为计算的总和生成一个唯一的观察器,并将其保存在vm中。_computedWatchers。
执行render函数时会访问sum属性,这样在执行initComputed时定义的getter方法会将Dep.target指向sum的watcher,并调用这个属性的具体方法sum。
在sum方法中访问this.count时,会调用this.count代理的get方法,this.count的dep会添加到sum的watcher中,而dep中的subs会添加这个watcher。
设置vm.count=2,调用计数代理的Set方法触发dep的notify方法。因为它是一个计算属性,所以它只是将watcher中的dirty设置为true。
最后一步vm.sum在访问其get方法时,知道sum的watcher.dirty为真,调用其watcher.evaluate()方法获取新值。
以上是vue computed的缓存实现原理的详细说明。关于vue computed的缓存实现的更多信息,请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。