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(){constcallbacks=[]//存放所有回调letpending=false//记录当前是否有回调在执行lettimerFunc//一个函数,包装了一个能添加到microtask的函数// 在这里执行所有的回调,而这个函数的执行时机在下一个事件循环中functionnextTickHandler(){pending=false// 将callbacks数组复制执行,因为如果在nextTick的回调函数中继续执行Vue.nextTick()// 则cb会不断被push到callbacks中,导致callbacks一直执行constcopies=callbacks.slice(0)callbacks.length=0for(leti=0;i<copies.length;i++){copies[i]()}}// the nextTick behavior leverages the microtask queue, which can be accessed// via either native Promise.then or MutationObserver.// MutationObserver has wider support, however it is seriously bugged in// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It// completely stops working after triggering a few times... so, if native// Promise is available, we will use it:/* istanbul ignore if */if(typeofPromise!=='undefined'&&isNative(Promise)){varp=Promise.resolve()varlogError=err=>{console.error(err)}// 如果能用Promise则通过Promise.then把nextTickHandler添加到microtasktimerFunc=()=>{p.then(nextTickHandler).catch(logError)if(isIOS)setTimeout(noop)}}elseif(typeofMutationObserver!=='undefined'&&(isNative(MutationObserver)||MutationObserver.toString()==='[object MutationObserverConstructor]')){// 如果能用MutationObserver,则人为的创建一个textNode,// 并让MutationObserver监听这个textNode,在timerFunc中改变这个textNode,// 由此触发MutationObserver的回调(这里涉及MutationObserver的工作方式,看看MDN文档就好),// 实现在下一次事件循环执行nextTickHandler的目的varcounter=1varobserver=newMutationObserver(nextTickHandler)vartextNode=document.createTextNode(String(counter))observer.observe(textNode,{characterData: true})timerFunc=()=>{counter=(counter+1)%2textNode.data=String(counter)}}else{//都不行只能用setTimeout实现,将nextTickHandler添加到macrotask中timerFunc=()=>{setTimeout(nextTickHandler,0)}}returnfunctionqueueNextTick(cb?: Function,ctx?: Object){let_resolvecallbacks.push(()=>{if(cb)cb.call(ctx)if(_resolve)_resolve(ctx)})if(!pending){
pending =truetimerFunc()}if(!cb&&typeofPromise!=='undefined'){returnnewPromise(resolve=>{_resolve=resolve})}}})()
2017-07-07更新
最近在看Under the hood: ReactJS,part-1又提到了事务这个概念,回过头来又看了一遍发现之前一直好奇的问题原来如此简单。关于react将需要更新的组件放到dirtyComponents这里可以理解,但是是在什么时机去更改batchingStrategy.isBatchingUpdates的值,或者说执行ReactUpdates.flushBatchedUpdates的时机,因为对于vue来说就是一个microtask,很好理解。现在才知道原来如果在react生命周期如componentDidMounted调用setState,其实componentDidMounted就处在一个事务中,那么当它执行完的时候就该执行transaciton.closeAll,在这里处理批量更新
Vue中的批量更新DOM
关于批量更新DOM文档中有这么一段介绍:
这么做可以有效避免无效的更新,比如一个数据在一个事件循环中多次改变,则如果按正常的策略会多次触发watcher中的回调,重新构建虚拟DOM进行diff处理,而事实上这是不必要的,其实重复的watcher只要执行一次。所以Vue利用js中的事件循环进行优化减少不必要的计算和DOM操作。
我主要阅读的关于
Vue.nextTick
这一块源码,来看一下Vue是如何利用事件循环的。在此之前需要明白两个东西,microtask
与MutationObserver
前者可以看知乎上的一个讨论Promise的队列与setTimeout的队列的有何关联。后者直接看看MDN就好,MutationObserver。首先看如何使用:
即传递一个回调函数,该函数在事件循环结束时调用,下面来看一下这块的源码(/src/core/util/env.js):
首先
nextTick
是一个自执行函数,返回值是queueNextTick()
函数(主要利用闭包来保存一些变量)。当Vue.nextTick()
执行时,执行的就是queueNextTick()
函数,在这里将回调放入callbacks
数组保存。并且用了一个pending标志位做了一次判断,它的主要作用是保证timerFunc()
这个函数在一轮事件循环中只执行一次(因为在执行timerFunc()前将它置为true,而只有下一轮事件循环开始时它才能被置为false),timerFunc()
这个函数仅仅包装了一个能添加到microtask的函数(如promise.then,MutationObserver),具体注释中有。关于选用哪种方式利用事件循环,从代码中可以看出,优先使用Promise,不存在则使用MutationObserver,都不存在则用setTimeout。清楚这个函数的工作原理差不多就明白了Vue异步更新DOM的原理了,因为Vue会把一轮事件循环(即一次task)中所有触发的watcher去重后添加到一个队列里,然后将这个队列交由
Vue.nextTick()
,即将这个队列添加到microtask中,这样在本次task结束后,按照规则就会取出所有的microtask执行它们,实现DOM的更新。React是如何做的?
react通过setState这个API改变state,那么它是如何工作的?
React 源码剖析系列 - 解密 setState这篇文章提到一个例子:
输出结果是
0 0 2 3
原因文章中也有提到。此外Change Detection And Batch Update这篇文章也有一个总结:
就是说如果方法是通过React调用的比如生命周期函数,React的事件处理等,那么会进行批量更新,自己调用的方法,比如setTimeout,xhr等则是连续更新。
最近在看Under the hood: ReactJS,part-1又提到了事务这个概念,回过头来又看了一遍发现之前一直好奇的问题原来如此简单。关于react将需要更新的组件放到
dirtyComponents
这里可以理解,但是是在什么时机去更改batchingStrategy.isBatchingUpdates
的值,或者说执行ReactUpdates.flushBatchedUpdates
的时机,因为对于vue来说就是一个microtask,很好理解。现在才知道原来如果在react生命周期如componentDidMounted
调用setState
,其实componentDidMounted
就处在一个事务中,那么当它执行完的时候就该执行transaciton.closeAll
,在这里处理批量更新可以参看这张图,来自Under the hood: ReactJS
参考链接
Vue源码详解之nextTick
vue早期源码学习系列之五:批处理更新DOM
The text was updated successfully, but these errors were encountered: