You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
exportconstnextTick=(function(){varcallbacks=[]varpending=falsevartimerFuncfunctionnextTickHandler(){pending=false// 之所以要slice复制一份出来是因为有的cb执行过程中又会往callbacks中加入内容// 比如$nextTick的回调函数里又有$nextTick// 这些是应该放入到下一个轮次的nextTick去执行的,// 所以拷贝一份当前的,遍历执行完当前的即可,避免无休止的执行下去varcopies=callbacks.slice(0)callbacks=[]for(vari=0;i<copies.length;i++){copies[i]()// 遍历执行回调}}/*--------------------------确定timerFunc---------------------------*//* 这一坨代码就是为了确定timerFunc*/// ios9.3以上的WebView的MutationObserver有bug,//所以在hasMutationObserverBug中存放了是否是这种情况if(typeofMutationObserver!=='undefined'&&!hasMutationObserverBug){varcounter=1// 创建一个MutationObserver,observer监听到dom改动之后后执行回调nextTickHandlervarobserver=newMutationObserver(nextTickHandler)vartextNode=document.createTextNode(counter)// 调用MutationObserver的接口,观测文本节点的字符内容observer.observe(textNode,{characterData: true})// 每次执行timerFunc都会让文本节点的内容在0/1之间切换,// 不用true/false可能是有的浏览器对于文本节点设置内容为true/false有bug?// 切换之后将新值赋值到那个我们MutationObserver观测的文本节点上去timerFunc=function(){counter=(counter+1)%2textNode.data=counter}}else{// webpack attempts to inject a shim for setImmediate// if it is used as a global, so we have to work around that to// avoid bundling unnecessary code.// webpack默认会在代码中插入setImmediate的垫片// 没有MutationObserver就优先用setImmediate,不行再用setTimeoutconstcontext=inBrowser
? window
: typeofglobal!=='undefined' ? global : {}timerFunc=context.setImmediate||setTimeout}/*--------------------------确定timerFunc---------------------------*/returnfunction(cb,ctx){varfunc=ctx
? function(){cb.call(ctx)}
: cbcallbacks.push(func)// 如果pending为true, 就其实表明本轮事件循环中已经执行过timerFunc(nextTickHandler, 0)if(pending)returnpending=truetimerFunc(nextTickHandler,0)}})()
深度解析vue的$nextTick的实现原理
前提
vue 中有一个我们经常会用到的api,nextTick。我们都知道他是个可以在Dom更新后才执行的回调,比如下面的代码:
每次用它的时候,我都会想,nextTick是怎么实现的呢,难道是监听了Dom的变化吗?于是我去看了下nextTick的实现源码,根据源码,我们可以详细了解下这个货。
(注:在阅读之前需要了解 事件循环机制 、microtask 和 task/macrotask 的基本概念,可参考我写的文章 JavaScript并发模型与Event Loop)
正文
前提中我们猜想是不是监听了Dom的更新,在HTML5中的确是有个api:MutationObserver,他可以监听Dom对象的变动(节点的删除、属性修改等) 。代码示例如下:
那么vue是不是用MutationObserver来监听Dom是否更新完毕的呢?然后我们打开vue的部分源码看看:
很明显我们在代码中看到了MutationObserver,但是MutationObserver监听Dom不对啊,源码中监听的是自己创建的文本节点,难道这个文本节点变化就能代表其他Dom的变化吗,很明显这个结论不成立。
其实nextTick的实现原理并不是基于MutationObserver,而是稍微借用的了MutationObserver的microtask特性,下面让我们看下nextTick的全部代码(代码版本为2.4.x):
我们可以看下中间的一坨代码,是为了确定timerFunc函数,而timerFunc函数的作用就是利用其microtask特性异步执行。但是为什么不直接用MutationObserver呢?对于这个问题,我们可以先看看下面的示例
在上面这一段代码运行后,控制台只输出了一个
'更新Dom'
,并没有输出1000个来,不是说好的数据改变,Dom就更新呢?其实吧,现在这种结果才是对的,假如同一个数据我改了1000次,Dom也更新1000次,那么对浏览器的压力是非常大的。最好就是现在这个样子的,不管数据怎么改变,在数据全部更新后再去更新Dom,这样只更新一次Dom就好了。那么问题又来了,怎么保证在全部数据更新后就更新Dom呢,怎么保证更新Dom后就能执行$nextTick的回调函数呢。
首先我们要知道,在数据更新后,vue模块中的watcher观测到数据变化后会执行
nextTick(flushBatcherQueue)
,flushBatcherQueue
则负责执行完成所有的dom更新操作(这一块的详细流程我会在下一篇文章详细说)。 也就是说在数据更新后就执行了nextTick
,我们根据nextTick的源码来看,flushBatcherQueue
被push到了callbacks
数组,又因为此时pending为false,便执行了timerFunc
,nextTickHandler
被添加了microtask队列。此时的状态是这样的:因为
nextTickHandler
被添加进了microtask队列,所以要等待全部代码执行完毕才能再执行nextTickHandler
,所以接下来会先执行this.$nextTick(cb)
,同样是调用nextTick
,也就是nextTick(cb)
,cb
被push进callbacks
,但此时pending
为true,并不会执行nextTickHandler
(因为每执行一次nextTickHandler
,都会往microtask队列里加一个任务,此时并没有必要)。所以此时的状态如下:在nextTick执行完后,若执行栈里为空,就会执行microtask队列的任务,在我们的例子中来看,microtask队列的任务只有一个
nextTickHandler
,于是就执行nextTickHandler
。而nextTickHandler
的主要代码是遍历执行callbacks
数组里的函数,也就是先执行flushBatcherQueue
将数据更新到Dom上,然后再执行nextTick的回调函数cb
,这样整个流程就下来了。下面我盗一张图来展示下主要流程:也就是说,更新Dom函数和nextTick的回调函数都在microtask队列里,而microtask队列只会在所有同步任务都执行完后才会执行,这样就保证了,Dom更新和nextTick的回调函数一定在数据更新后,而
callbacks
数组里的顺序保证了nextTick的回调函数一定在Dom更新后(特别提醒:Dom更新是同步的,ui渲染才是异步的)。结论
用MutationObserver只是因为他的microtask特性,在源码中我们也看到了假如不支持MutationObserver,还有setImmediate等选择,最后实在不行只能用setTimeOut(setTimeOut只有Macrotasks特性,没办法才用他 )。其实假如有更好的选择,MutationObserver也会被换掉,实际上在Vue2.5.x以后,MutationObserver也确实因为某些兼容问题被去掉了。
参考文献
The text was updated successfully, but these errors were encountered: