vue异步更新dom原理,vue异步改同步
本文主要介绍vue异步更新的实现原理,帮助大家更好的理解和使用Vue。感兴趣的朋友可以了解一下。
最近面试总是被问到这样一个问题:使用vue时,将我在for循环中声明的变量从1增加到100,然后在页面上显示I。页面上的I是从1跳到100,还是会发生什么?当然答案是只会显示100,不会有跳转过程。
如何让页面显示从1到100的过程?就是用setTimeout或者Promise.then等方法模拟一下。
讲道理。如果不使用vue单独运行这个程序,输出肯定是从1到100,但是为什么在vue中不一样?
for(设I=1;i=100i ){
console.log(一);
}
这涉及到Vue底层的异步更新原理,也谈到了nextTick的实现。不过在说nextTick之前,有必要先介绍一下JS的事件操作机制。
JS运行机制
众所周知,JS是基于事件循环的单线程语言。实施步骤大致如下:
代码执行时,所有同步任务都在主线程上执行,形成执行栈;
在主线程之外还有一个任务队列。只要异步任务有运行结果,就会在任务队列中放置一个事件。
一旦执行堆栈中的所有同步任务完成(主线程代码完成),主线程将不会空闲,而是读取任务队列。此时,异步任务已经完成,等待执行。
主线程不断重复上述步骤。
我们把执行主线程一次的进程叫做一个Tick,所以nextTick就是下一个Tick的意思,也就是说有下一个tick的场景就是下一个tick我们要做什么事情的时候。
的所有异步任务结果都通过任务队列进行调度。任务分为两类:宏观任务和微观任务。它们之间的执行规则是,每个宏任务之后,所有的微任务都要清空。常见的宏观任务包括settimeout/消息通道/postmessage/setimmediate,微观任务包括MutationObsever/Promise.then
如果想彻底研究事件周期,推荐Jake在JavaScript全球开发者大会上的演讲,一定要理解!
nextTick原理
派发更新
众所周知,vue的响应能力依赖于收集和分发更新。修改数组后的调度更新过程会触发setter的逻辑,执行dep.notify():
//src/core/observer/watcher.js
类Dep
通知(){
//subs是Watcher实例的数组。
const subs=this.subs.slice()
for(设i=0,l=subs.lengthil;i ){
分句[i]。更新()
}
}
}
在subs中遍历每个Watcher实例,然后调用实例的update方法。让我们来看看update是如何更新的:
类监视器{
update() {
.
//判断各种情况后
否则{
队列观察器(this)
}
}
}
更新后又去了queueWatcher,继续看queueWatcher做了什么(希望不要继续依偎了:
//queueWatcher在src/core src/core/observer/scheduler . js中定义
常量队列:ArrayWatcher=[]
let has: { [key: number]:true }={}
让等待=假
让flushing=false
让索引=0
导出函数queueWatcher(watcher: Watcher) {
const id=watcher.id
//根据id是否重复进行优化
if(有[id]==null){
has[id]=true
如果(!冲洗){
queue.push(观察器)
}否则{
设i=queue.length - 1
while(i索引队列[i]。id watcher.id){
异
}
queue.splice(i 1,0,观察器)
}
如果(!等待){
等待=真
//flushSchedulerQueue函数:刷新两个队列并运行观察器
nextTick(flushSchedulerQueue)
}
}
}
这里,在推送pushwatchers时,根据id和flushing优化队列。不是每次数据改变时触发观察器的回调,而是先将这些观察器添加到队列中,然后在nextTick之后执行flushSchedulerQueue。
FlushSchedulerQueue函数是对保存更新事件的队列进行一些处理,使更新能够满足Vue更新的生命周期。
这也解释了为什么for循环不能导致页面更新,因为for是主线程的代码,它会在数据更改开始时被推入队列。执行for中的代码后,如果I的值更改为100,vue将转到nextTick(flushSchedulerQueue)步骤。
nextTick源码
然后打开vue2.x的源代码,目录core/util/next-tick.js,代码量很小,只有110行注释,很好理解。
常量回调=[]
让待定=假
导出函数nextTick (cb?函数,ctx?对象){
让_解决
callbacks.push(()={
if (cb) {
尝试{
cb .呼叫(ctx)
} catch (e) {
handleError(e,ctx, nextTick )
}
} else if (_resolve) {
_解析(ctx)
}
})
如果(!待定){
待定=真
定时器函数()
}
首先将传入的回调函数cb(上一节中的flushSchedulerQueue)压入回调数组,最后由timerFunc函数求解一次。
让计时器运行
如果(类型的承诺!==undefined 是Native(Promise)) {
const p=Promise.resolve()
timerFunc=()={
p.then(刷新回调)
if (isIOS)设置超时(noop)
}
isUsingMicroTask=true
} else if(!这是一种变异观察者!==未定义 (
isNative(变异观察器)
//PhantomJS和iOS 7.x
mutationobserver . tostring()===[object mutationobserver constructor]
)) {
让计数器=1
const observer=new mutation observer(flush callbacks)
const textNode=document . create textNode(String(counter))
observer.observe(textNode,{
characterData: true
})
timerFunc=()={
计数器=(计数器1) % 2
textNode.data=String(计数器)
}
isUsingMicroTask=true
} else if (typeof setImmediate!==undefined 是Native(setImmediate)) {
timerFunc=()={
setImmediate(flushCallbacks)
}
}否则{
timerFunc=()={
setTimeout(flushCallbacks,0)
}
}
在timerFunc下,用ielse的一大块来判断在不同设备、不同情况下,用哪些特性来实现异步任务:先检查Promise是否是原生的,再检查MutationObserver是否是原生的。如果其他方法都失败了,你只能尝试执行宏任务。首先,setImmediate是一个只有IE和Edge版本才有的功能,如果没有,它最终将被降级为setTimeout 0。
callbacks之所以在下一个Tick中不直接执行回调函数,是为了保证下一个tick在同一个tick中多次执行,这样就不会启动多个异步任务,所有这些异步任务都压入一个同步任务中,在下一个tick中完成。
nextTick使用
NextTick不仅是vue的源文件,也是vue的一个全局API。让我们来看看如何使用它。
当设置了vm.someData=new value 时,组件不会立即重新呈现。当队列被刷新时,组件将在下一个事件循环周期被更新。在大多数情况下,我们不需要关心这个过程,但如果你想基于更新后的DOM状态做一些事情,可能会有点棘手。虽然Vue.js通常鼓励开发者以数据驱动的方式思考,避免直接接触DOM,但有时我们不得不这样做。要在数据更改后等待Vue完成更新DOM,可以在数据更改后立即使用Vue.nextTick(callback)。这样,回调函数将在DOM更新完成后被调用。
官网用例:
div id=示例“{message}}/div
var vm=new Vue({
el: #example ,
数据:{
消息:“123”
}
})
Vm.message=新消息//更改数据
vm。$el.textContent===新邮件//false
Vue.nextTick(function () {
vm。$el.textContent===新邮件//true
})
而且因为$nextTick()返回一个Promise对象,所以还可以使用async/await语法来处理事件,这非常方便。
以上是对Vue异步更新实现原理的详细说明。更多关于vue异步更新的信息,请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。