vue异步更新原理,vue 异步数据更新
本文主要介绍vue源代码批量异步更新策略的相关信息。vue异步更新是我们在日常开发中经常遇到的一个功能。有需要的朋友可以参考一下。
vue异步更新源代码中有与事件循环、宏任务、微任务相关的概念,我们先来看看这些概念。
一、事件循环、宏任务、微任务
1.事件循环(Event Loop):浏览器定制的工作机制,用于协调事件处理、脚本执行、网络请求和渲染等任务。
2.宏任务任务:表示离散和独立的工作单元。当浏览器完成一个宏任务时,它将在下一个宏任务开始之前重新呈现页面。主要包括创建文档对象、解析HTML、执行主线JS代码以及页面加载、输入、网络事件、定时器等各种事件。
3.微任务:微任务是一个较小的任务,在当前宏任务执行后立即执行。如果有微任务,浏览器会在完成微任务后重新渲染。微任务的例子有Promise回调函数、DOM改变等。
执行过程:宏任务完成=微任务执行=页面重新渲染=新一轮宏任务执行。
任务执行顺序示例:
//第一个宏任务进入主线程
console . log(“1”);
//扔进宏事件队列
setTimeout(function() {
console . log(“2”);
process.nextTick(function() {
console . log(“3”);
})
新承诺(功能(解决){
console . log(“4”);
resolve();
}).then(function() {
console.log(5 )
})
})
//微事件1
process.nextTick(function() {
console.log(6 )。
})
//主线程直接执行
新承诺(功能(解决){
console . log( 7 );
resolve();
}).then(function() {
//微事件2
console.log(8 )
})
//扔进宏事件队列
setTimeout(function() {
console . log( 9 );
process.nextTick(function() {
console . log( 10 );
})
新承诺(功能(解决){
console . log( 11 );
resolve();
}).then(function() {
console.log(12 )
})
})
//1,7,6,8,2,4,3,5,9,11,10,12
解析:
第一个宏任务
第一个宏任务进入主线程,打印1。
SetTimeout被抛出到宏任务队列中
Process.nextTick被抛出到微任务队列中
新承诺直接执行,打印7
Promise then事件被扔进微任务队列。
SetTimeout被抛出到宏任务队列中
当第一个宏任务完成后,开始微任务。
执行process.nextTick,print 6
执行承诺然后事件,打印8
微任务执行完毕,清空微任务队列,渲染页面,进入下一个宏任务setTimeout。
执行打印2
Process.nextTick被抛出到微任务队列中
新承诺直接执行,打印4
Promise then事件被扔进微任务队列。
当第二个宏任务完成时,微任务开始。
执行process.nextTick,print 3
执行承诺然后事件,打印5
微任务执行完毕后,清空微任务的队列,渲染页面,进入下一个宏任务setTimeout,重复上述类似过程,打印出9,11,10,12。
二、Vue异步批量更新过程
1.解析:当检测到数据变化时,vue会打开一个队列,在队列中存储相关的watcher,在callbacks队列中存储回调函数,异步执行回调函数,遍历watcher队列进行渲染。
异步:vue在更新DOM时异步执行。只要感知到数据变化,Vue就会打开一个队列,缓冲在同一个事件周期中发生的所有数据变化。
Batch:如果同一个watcher被多次触发,那么它只会被推入队列一次。以避免不必要的计算和DOM操作。然后,在下一个事件循环“tick”中,vue刷新队列以执行实际工作。
异步策略:Vue内部尝试使用原生Promise.then,对异步队列使用MutationObserver和setImmediate。如果执行环境不支持,它将使用setTimeout(fn,0)来代替。也就是我们会尝试先用微任务,再用宏任务。
异步批量更新流程图:
三、vue批量异步更新源码
异步更新:整个过程相当于将臭袜子放到盆子里,最后一起洗。
1.当一个数据更新时,会依次执行以下代码:
(1)触发Data.set()
(2)调用dep.notify():遍历所有相关的观察者,调用watcher.update()。
核心/观察者/索引。js:
通知(){
const subs=this.subs.slice()
//如果未运行异步,则不会在调度程序中对潜水艇进行排序
if (process.env.NODE_ENV!==生产!config.async) {
//排序,确保它们按正确的顺序执行
subs.sort((a,b)=a.id - b.id)
}
//遍历相关观察者,并调用看守人更新
对于(设i=0,l=subs.lengthI li ) {
分句[我].更新()
}
}
(3)执行watcher.update():判断是立即更新还是异步更新。若为异步更新,调用队列观察器(这个),将看守人入队,放到后面一起更新。
核心/观察者/观察者。js:
update () {
/*伊斯坦布尔忽略else */
如果(this.lazy) {
this.dirty=true
} else if (this.sync) {
//立即执行渲染
this.run()
}否则{
//观察器入队操作,后面一起执行渲染
队列观察器(这个)
}
}
(4)执行队列观察器(这个):观察器进行去重等操作后,添加到队列中,调用nextTick(flushSchedulerQueue)执行异步队列,传入回调函数flushSchedulerQueue。
核心/观察者/调度程序。js:
函数queueWatcher (watcher: Watcher) {
//有标识,判断该看守人是否已在,避免在一个队列中添加相同的看守人
const id=watcher.id
如果(有[id]==null) {
has[id]=true
//冲洗标识,处理看守人渲染时,可能产生的新观察者。
如果(!冲洗){
//将当前看守人添加到异步队列
队列.推送(观察器)
}否则{
//产生新的看守人就添加到排序的位置
设i=queue.length - 1
而(我索引队列[我].id watcher.id) {
异
}
queue.splice(i 1,0,观察器)
}
//将刷新排队
//正在等待标识,让所有的看守人都在一个滴答声内进行更新。
如果(!等待){
等待=真
if (process.env.NODE_ENV!==生产!config.async) {
flushSchedulerQueue()
返回
}
//执行异步队列,并传入回调
nextTick(flushSchedulerQueue)
}
}
}
(5)执行下一个节拍(cb):将传进去的flushSchedulerQueue函数处理后添加到复试队列中,调用定时器功能启动异步执行任务。
core/util/next-tick.js:
函数nextTick (cb?函数,ctx?对象){
让_解决
//此处的复试就是队列(回调数组),将传入的flushSchedulerQueue方法处理后添加到回调数组
callbacks.push(()={
if (cb) {
尝试{
cb。呼叫(ctx)
} catch (e) {
handleError(e,ctx, nextTick )
}
} else if (_resolve) {
_解析(ctx)
}
})
如果(!待定){
待定=真
//启动异步执行任务,此方法会根据浏览器兼容性,选用不同的异步策略
定时器函数()
}
//$流量-禁用-线路
如果(!可换股债券类型的承诺!==未定义){
返回新承诺(resolve={
_resolve=解决
})
}
}
(6)timerFunc():根据浏览器兼容性,选用不同的异步方式去执行刷新回调。由于宏任务耗费的时间是大于微任务的,所以先选用微任务的方式,都不行时再使用宏任务的方式,
core/util/next-tick.js:
让计时器运行
//支持承诺则使用承诺异步的方式执行刷新回调
如果(类型的承诺!==未定义是Native(Promise)) {
const p=Promise.resolve()
timerFunc=()={
p .然后(刷新回调)
如果(isIOS)设置超时(noop)
}
isUsingMicroTask=true
} else if(!这是一种变异观察者!==未定义(
isNative(变异观察器)
变异观察者。tostring()===[对象变异观察器构造函数]
)) {
让计数器=1
const observer=新的变异观察器(刷新回调)
const textNode=document。创建textNode(String(counter))
observer.observe(textNode,{
characterData: true
})
timerFunc=()={
计数器=(计数器1) % 2
textNode.data=String(计数器)
}
isUsingMicroTask=true
} else if (typeof setImmediate!==未定义是Native(setImmediate)) {
timerFunc=()={
setImmediate(flushCallbacks)
}
}否则{
//实在不行再使用定时器的异步方式
timerFunc=()={
setTimeout(flushCallbacks,0)
}
}
(7)刷新回调:异步执行复试队列中所有函数
core/util/next-tick.js:
//循环复试队列,执行里面所有函数flushSchedulerQueue,并清空队列
函数flushCallbacks () {
待定=假
const copies=callbacks.slice(0)
回调长度=0
对于(设I=0;一份,长度;i ) {
份数[我]()
}
}
(8)flushSchedulerQueue():遍历看守人队列,执行watcher.run()
watcher.run():真正的渲染
函数flushSchedulerQueue() {
currentFlushTimestamp=get now();
flushing=true
让观察者,id;
//排序,先渲染父节点,再渲染子节点
//这样可以避免不必要的子节点渲染,如:父节点中控制显示为错误的的子节点,就不用渲染了
queue.sort((a,b)=a . id-b . id);
//不缓存长度,因为可能会推送更多的观察器
//当我们运行现有的观察器时
//遍历所有看守人进行批量更新。
对于(索引=0;索引队列长度;索引){
watcher=queue[index];
if (watcher.before) {
观察者。之前();
}
id=watcher.id
has[id]=null;
//真正的更新函数
观察者。run();
//在开发版本中,检查并停止循环更新。
if (process.env.NODE_ENV!==生产有[id]!=null) {
circular[id]=(circular[id] 0)1;
if(circular[id]MAX _ UPDATE _ COUNT){
警告(
您可能会有一个无限更新循环
(观察者。用户
?`在带有表达式" ${watcher.expression} "的观察器中
:`在组件渲染函数中.`),
watcher.vm
);
打破;
}
}
}
//在重置状态之前保留发布队列的副本
const activated queue=activated children。slice();
const更新队列=queue。slice();
resetSchedulerState();
//调用组件更新并激活挂钩
callactivatedbooks(激活队列);
callUpdatedHooks(更新队列);
//devtool挂钩
/*伊斯坦布尔忽略if */
if (devtools config.devtools) {
开发工具。发出(“flush”);
}
}
(9)更新组件():watcher。运行()经过一系列的转圈,执行更新组件,更新组件中执行render(),让组件重新渲染,再执行_update(vnode),再执行补丁()更新界面。
(10)_更新():根据是否有虚拟节点分别执行不同的补丁。
四、Vue.nextTick(callback)
1.Vue.nextTick(回调)作用:获取更新后的真正的数字正射影像图元素。
由于某视频剪辑软件在更新数字正射影像图时是异步执行的,所以在修改数据之后,并不能立刻获取到修改后的数字正射影像图元素。为了获取到修改后的数字正射影像图元素,可以在数据变化之后立即使用Vue.nextTick(回调).
2.为什么Vue .$nextTick能够获取更新后的多姆。
因为Vue .$nextTick其实就是调用下一滴答方法,在异步队列中执行回调函数。
vue。原型。$ next tick=Function(fn:Function){
返回nextTick(fn,this);
};
3.使用Vue .$nextTick
例子1:
模板
p id= test"{ foo } }/p
/模板
脚本
导出默认值{
data(){
返回{
福:"福"
}
},
已安装(){
让测试=文档。查询选择器(" # test ");
this.foo= foo1
//vue在更新数字正射影像图时是异步进行的,所以此处数字正射影像图并未更新
控制台。日志(测试。innerhtml:测试。innerhtml’);
这个. nextTick(()={
//nextTick回调是在数字正射影像图更新后调用的,所以此处数字正射影像图已经更新
控制台。日志(下一个滴答:测试。innerhtml:测试。innerhtml’);
})
}
}
/脚本
执行结果:
test.innerHTML:foo
nextTick:test.innerHTML:foo1
例子第二:
模板(模板)
p id= test“{ foo } }/p
/模板-样板
脚本编写
导出默认值[
日期()
返回
福:“福”
}
},
已挂载()
让测试=文档。查询选择器(" # test ");
这个。福=福1
//检视在更新多姆时是异步进行的,所以此处多姆并未更新
控制台。日志( 1。测试。内部html:“测试。内部html ;
这一点. nextTick()
//nextTick(下一个打勾)回调是在多姆更新后调用的,所以此处多姆已经更新
控制台。log( nexttick:test。内部html:“测试。内部html ;
})
这个。福=福2
//此处多姆并未更新,且先于异步回调函数前执行
控制台。日志( 2。测试。内部html:“测试。内部html ;
}
}
/脚本
执行结果:
1 .测试。html内:foo
2 .测试。html内:foo
nexttick:测试。肝内动脉:foo 2
例子第三:
模板(模板)
p id= test“{ foo } }/p
/模板-样板
脚本编写
导出默认值[
日期()
返回
福:“福”
}
},
已挂载()
让测试=文档。查询选择器(" # test ");
这一点. nextTick()
//nextTick(下一个打勾)回调是在触发更新之前就放入回调函数队列,
//压根没有触发看守人更新以及以后的一系列操作,所以也就没有执行到最后的watcher.run()实行渲染
//所以此处多姆并未更新
控制台。log( nexttick:test。内部html:“测试。内部html ;
})
这个。福=福1
//检视在更新多姆时是异步进行的,所以此处多姆并未更新
控制台。日志( 1。测试。内部html:“测试。内部html ;
这个。福=福2
//此处多姆并未更新,且先于异步回调函数前执行
控制台。日志( 2。测试。内部html:“测试。内部html ;
}
}
/脚本
执行结果:
1 .测试。html内:foo
2 .测试。html内:foo
nexttick:测试。html内:foo
4、 nextTick与其他异步方法美元
nextTick(下一个打勾)是模拟的异步任务,所以可以用答应我你好七个月来实现和这一点. nextTick美元相似的效果。
例子1 .执行下列操作:
模板(模板)
p id= test“{ foo } }/p
/模板-样板
脚本编写
导出默认值[
日期()
返回
福:“福”
}
},
已挂载()
让测试=文档。查询选择器(" # test ");
这一点. nextTick()
//nextTick(下一个打勾)回调是在触发更新之前就放入回调函数队列,
//压根没有触发看守人更新以及以后的一系列操作,所以也就没有执行到最后的watcher.run()实行渲染
//所以此处多姆并未更新
控制台。log( nexttick:test。内部html:“测试。内部html ;
})
这个。福=福1
//检视在更新多姆时是异步进行的,所以此处多姆并未更新
控制台。日志( 1。测试。内部html:“测试。内部html ;
这个。福=福2
//此处多姆并未更新,且先于异步回调函数前执行
控制台。日志( 2。测试。内部html:“测试。内部html ;
Promise.resolve().然后()
控制台。日志(承诺:测试。内部html:“测试。内部html ;
});
setTimeout()
控制台。log( settimeout:test。内部html:“测试。内部html ;
});
}
}
/脚本
执行结果:
1 .测试。html内:foo
2 .测试。html内:foo
nexttick:测试。html内:foo
承诺:测试。肝内动脉:foo 2
settimeout:测试。肝内动脉:foo 2
例子第二:
模板(模板)
p id= test“{ foo } }/p
/模板-样板
脚本编写
导出默认值[
日期()
返回
福:“福”
}
},
已挂载()
让测试=文档。查询选择器(" # test ");
//承诺(承诺)你好七个月依旧是等到多姆更新后再执行
Promise.resolve().然后()
控制台。日志(承诺:测试。内部html:“测试。内部html ;
});
setTimeout()
控制台。log( settimeout:test。内部html:“测试。内部html ;
});
这一点. nextTick()
//nextTick(下一个打勾)回调是在触发更新之前就放入回调函数队列,
//压根没有触发看守人更新以及以后的一系列操作,所以也就没有执行到最后的watcher.run()实行渲染
//所以此处多姆并未更新
控制台。log( nexttick:test。内部html:“测试。内部html ;
})
这个。福=福1
//检视在更新多姆时是异步进行的,所以此处多姆并未更新
console . log( 1 . test . innerhtml: test . innerhtml );
this.foo= foo2
//这里不更新DOM,在异步回调函数之前执行。
console . log( 2 . test . innerhtml: test . innerhtml );
}
}
/脚本
执行结果:
1.test.innerHTML:foo
test.innerHTML:foo
nextTick:test.innerHTML:foo
Promise:test.innerHTML:foo2
setTimeout:test.innerHTML:foo2
总结
这就是本文关于vue的批量异步更新策略的源代码。更多关于vue批量异步更新策略的信息,请搜索我们之前的文章或者继续浏览下面的相关文章。希望大家以后能多多支持我们!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。