总结:
因为如果使用macrotask, vue的batch (一个一个执行nextTick内部维护数组中的函数) 的这个操作会放在下一个macrotask中执行,
当你调用nextTick的script解析完之后马上要开始执行微任务,微任务后又是宏任务微任务,(这个中间可能执行了很多个其他macroTask)直到你要执行的那个macrotask 进行batch,
最后到ui rendering,batch过程中dom元素变更的数据才最后体现在界面上
在真实浏览器环境中这个中间可能会做很多操作,而且真实环境很复杂浏览器甚至可能有多个task queue,这个间隔时间可能会很长
如果使用microtask,vue的batch操作会在你script解析完后立即执行(因为script是一个macrotask),那么距离下一个ui rendering的时间会相对很短,
避免了一些边界情况。
jsfiddle1和jsfiddle2。
拿这两个例子,是两个不同的vuejs版本,第一个示例中使用microtask 第二个示例使用macrotask
==============================================
资料(转)
...
那问题又来了,为何必定要microtask?task能够吗?(macrotask和task是一回事哈,HTML5标准里甚至都没有macrotask这个词)。
哈,如今恰好有个例子,Vue一开始曾经改过nextTick的实现。咱们来看看这两个jsFiddle:jsfiddle1和jsfiddle2。
两个fiddle的实现如出一辙,就是让那个绝对定位的黄色元素起到一个fixed定位的效果:绑定scroll事件,每次滚动的时候,计算当前滚动的位置并更改到那个绝对定位元素的top属性上去。你们本身试试滚动几下,对比下效果,你就会发现第一个fiddle中的黄元素是稳定不动的,fixed很好。然后一个fiddle中就有问题了,黄色元素上下晃动,彷佛跟不上咱们scroll的节奏,总要慢一点,虽然最后停下滚动时位置是对的。
上述两个例子实际上是在这个issue中找到的,第一个jsfiddle使用的版本是Vue 2.0.0-rc.6,这个版本的nextTick实现是采用了MO,然后由于IOS9.3的WebView里的MO有bug,因而尤雨溪更改了实现,换成了window.postMessage
,也就是后一个fiddle所使用的Vue 2.0.0-rc.7。后来尤雨溪了解到window.postMessage
是将回调放入的macrotask 队列。这就是问题的根源了。
HTML中的UI事件、网络事件、HTML Parsing等都是使用的task来完成,所以每次scroll事件触发后,在当前的task里只是完成了把watcher加入队列和把清空watcher的flushBatcherQueue做为异步回调传入nextTick。
若是nextTick使用的是microtask,那么在task执行完毕以后就会当即执行全部microtask,那么flushBatcherQueue(真正修改DOM)便得以在此时当即完成,然后,当前轮次的microtask所有清理完成时,执行UI rendering,把重排重绘等操做真正更新到DOM上(后文会细说)。(注意,页面的滚动效果并不须要重绘哈。重绘是当你修改了UI样式、DOM结构等等,页面将样式呈现出来,别晕了。)
若是nextTick使用的是task,那么会在当前的task和全部microtask执行完毕以后才在之后的某一次task执行过程当中处理flushBatcherQueue,那个时候才真正执行各个指令的修改DOM操做,但那时为时已晚,错过了屡次触发重绘、渲染UI的时机。并且浏览器内部为了更快的响应用户UI,内部多是有多个task queue的:
For example, a user agent could have one task queue for mouse and key events (the user interaction task source), and another for everything else. The user agent could then give keyboard and mouse events preference over other tasks three quarters of the time, keeping the interface responsive but not starving other task queues, and never processing events from any one task source out of order.
而UI的task queue的优先级可能更高,所以对于尤雨溪采用的window.postMessage
,甚至可能已经屡次执行了UI的task,都没有执行window.postMessage
的task,也就致使了咱们更新DOM操做的延迟。在重CPU计算、UI渲染任务状况下,这一延迟达到issue观测到的100毫秒到1秒的级别是彻底课可能的。所以,使用task来实现nextTick是不可行的,而尤雨溪也撤回了这一次的修改,后续的nextTick实现中,依然是使用的Promise.then和MO。
task microtask和每轮event loop以后的UI Render
我最近认真阅读了一下HTML5规范,仍是来讲一说task和microtask处理完成以后的UI渲染过程,讲一下每次task执行和全部microtask执行完毕后使如何完成UI Render的。
先上HTML标准原文:
比较典型的task有以下这些
- Events
Dispatching an Event object at a particular EventTarget object is often done by a dedicated task. Not all events are dispatched using the task queue, many are dispatched during other tasks. - Parsing
The HTML parser tokenizing one or more bytes, and then processing any resulting tokens, is typically a task. - Callbacks
Calling a callback is often done by a dedicated task. - Using a resource
When an algorithm fetches a resource, if the fetching occurs in a non-blocking fashion then the processing of the resource once some or all of the resource is available is performed by a task. - Reacting to DOM manipulation
Some elements have tasks that trigger in response to DOM manipulation, e.g. when that element is inserted into the document.
此外,还包括setTimeout, setInterval, setImmediate, window.postMessage等等。
上述Reacting to DOM manipulation并非说你执行DOM操做时就会把这个DOM操做的执行当成一个task。是那些异步的reacting会被当作task。
HTML5标准:task、microtask和UI render的具体执行过程以下:
An event loop must continually run through the following steps for as long as it exists:
1.Select the oldest task on one of the event loop's task queues, if any, ignoring, in the case of a browsing context event loop, tasks whose associated Documents are not fully active. The user agent may pick any task queue. If there is no task to select, then jump to the microtasks step below.
2.Set the event loop's currently running task to the task selected in the previous step.
3.Run: Run the selected task.
4.Set the event loop's currently running task back to null.
5.Remove the task that was run in the run step above from its task queue.
6.Microtasks: Perform a microtask checkpoint. //这里会执行全部的microtask
7.Update the rendering: If this event loop is a browsing context event loop (as opposed to a worker event loop), then run the following substeps.
7.1 Let now be the value that would be returned by the Performance object's now() method.
7.2 Let docs be the list of Document objects associated with the event loop in question, sorted arbitrarily except that the following conditions must be met:
7.3 If there are top-level browsing contexts B that the user agent believes would not benefit from having their rendering updated at this time, then remove from docs all Document objects whose browsing context's top-level browsing context is in B.
7.4 If there are a nested browsing contexts B that the user agent believes would not benefit from having their rendering updated at this time, then remove from docs all Document objects whose browsing context is in B.
7.5 For each fully active Document in docs, run the resize steps for that Document, passing in now as the timestamp. [CSSOMVIEW]
7.6 For each fully active Document in docs, run the scroll steps for that Document, passing in now as the timestamp. [CSSOMVIEW]
7.7 For each fully active Document in docs, evaluate media queries and report changes for that Document, passing in now as the timestamp. [CSSOMVIEW]
7.8 For each fully active Document in docs, run CSS animations and send events for that Document, passing in now as the timestamp. [CSSANIMATIONS]
7.9 For each fully active Document in docs, run the fullscreen rendering steps for that Document, passing in now as the timestamp. [FULLSCREEN]
7.10 For each fully active Document in docs, run the animation frame callbacks for that Document, passing in now as the timestamp.
7.11 For each fully active Document in docs, run the update intersection observations steps for that Document, passing in now as the timestamp. [INTERSECTIONOBSERVER]
7.12 For each fully active Document in docs, update the rendering or user interface of that Document and its browsing context to reflect the current state.
8.If this is a worker event loop (i.e. one running for a WorkerGlobalScope), but there are no tasks in the event loop's task queues and the WorkerGlobalScope object's closing flag is true, then destroy the event loop, aborting these steps, resuming the run a worker steps described in the Web workers section below.
9.Return to the first step of the event loop.
解释一下:第一步,从多个task queue中的一个queue里,挑出一个最老的task。(由于有多个task queue的存在,使得浏览器能够完成咱们前面说的,优先、高频率的执行某些task queue中的任务,好比UI的task queue)。
而后2到5步,执行这个task。
第六步, Perform a microtask checkpoint. ,这里会执行完microtask queue中的全部的microtask,若是microtask执行过程当中又添加了microtask,那么仍然会执行新添加的microtask,固然,这个机制好像有限制,一轮microtask的执行总量彷佛有限制(1000?),数量太多就执行一部分留下的之后再执行?这里我不太肯定。
第七步,Update the rendering:
7.2到7.4,当前轮次的event loop中关联到的document对象会保持某些特定顺序,这些document对象都会执行须要执行UI render的,可是并非全部关联到的document都须要更新UI,浏览器会判断这个document是否会从UI Render中获益,由于浏览器只须要保持60Hz的刷新率便可,而每轮event loop都是很是快的,因此不必每一个document都Render UI。
7.5和7.6 run the resize steps/run the scroll steps不是说去执行resize和scroll。每次咱们scoll的时候视口或者dom就已经当即scroll了,并把document或者dom加入到 pending scroll event targets中,而run the scroll steps具体作的则是遍历这些target,在target上触发scroll事件。run the resize steps也是类似的,这个步骤是触发resize事件。
7.8和7.9 后续的media query, run CSS animations and send events等等也是类似的,都是触发事件,第10步和第11步则是执行咱们熟悉的requestAnimationFrame回调和IntersectionObserver回调(第十步仍是挺关键的,raf就是在这执行的!)。
7.12 渲染UI,关键就在这了。
第九步 继续执行event loop,又去执行task,microtasks和UI render。
更新:找到一张图,不过着重说明的是整个event loop,没有细说UI render。