masterkong / blog

文章区
21 stars 2 forks source link

Vue.$nextTick背后的Event Loop,Tasks, microtasks #4

Open masterkong opened 6 years ago

masterkong commented 6 years ago

Vue.$nextTick背后的Event Loop,Tasks, microtasks

前言

本文内容主要来源于Tasks, microtasks, queues and scheduleswhatwg,是对来源文章的总结归纳。

Vue 2.5.1对 nextTick的实现改用了SetImmediate和MessageChannel来实现。

// An asynchronous deferring mechanism. // In pre 2.4, we used to use microtasks (Promise/MutationObserver) // but microtasks actually has too high a priority and fires in between // supposedly sequential events (e.g. #4521, #6690) or even between // bubbling of the same event (#6566). Technically setImmediate should be // the ideal choice, but it's not available everywhere; and the only polyfill // that consistently queues the callback after all DOM events triggered in the // same loop is by using MessageChannel.

为什么会有Event Loop

说到Event Loop,很多人应该读过阮一峰的JavaScript 运行机制详解:再谈Event Loop。简单来说JavaScript是单线程的,但是它的任务却是多种多样的,可以响应用户的操作与用户互动,可以操作DOM,可以发送XHR请求并且异步响应...等等。那这些任务发生的时间和顺序都不是固定的,就需要Event Loop来统一进行处理。 JavaScript 的并发模型基于"事件循环"。这个模型与像 C 或者 Java 这种其它语言中的模型截然不同。 并发模型与事件循环

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There are two kinds of event loops: those for browsing contexts, and those for workers.

There must be at least one browsing context event loop per user agent, and at most one per unit of related similar-origin browsing contexts.

A browsing context event loop always has at least one browsing context. If such an event loop's browsing contexts all go away, then the event loop goes away as well. A browsing context always has an event loop coordinating its activities.

Worker event loops are simpler: each worker has one event loop, and the worker processing model manages the event loop's lifetime. whatwg-event-loop

event loop主要有两种,一种是浏览上下文事件循环,这个在浏览器页面必须至少有一个,如果页面有iframe标签,则iframe页面也有自己的的browsing context event loop。另一种是线程事件循环,这个主要就是Web Workers,每个Worker都有自己的event loop。

task、 task queues、microtask queue

上面提过Event Loop是处理task的,而task需要先放在queue里等待调度,就有了task queues和microtask queue。

可能有人在别的地方也看到过macrotask,但是macrotask和task是一回事,HTML5 Event loops标准里是没有macrotask这个词。

An event loop has one or more task queues. A task queue is an ordered list of tasks, which are algorithms that are responsible for such work as:

Events Dispatching an Event object at a particular EventTarget object is often done by a dedicated task. Note: 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.

一个event loop可以有一个或多个task queues,除了上面列举的功能会产生task之外,setTimeout、setInterval、setImmediate、MessageChannel、I/O、UI渲染也会产生task。

Tasks are scheduled so the browser can get from its internals into JavaScript/DOM land and ensures these actions happen sequentially. Between tasks, the browser may render updates.

A task is intended for a specific event loop: the event loop that is handling tasks for the task's associated Document or worker.

When a user agent is to queue a task, it must add the given task to one of the task queues of the relevant event loop.

Each task is defined as coming from a specific task source. All the tasks from one particular task source and destined to a particular event loop (e.g. the callbacks generated by timers of a Document, the events fired for mouse movements over that Document, the tasks queued for the parser of that Document) must always be added to the same task queue, but tasks from different task sources may be placed in different task queues.

Example: 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.

再来看下microtask queue

Each event loop has a microtask queue. A microtask is a task that is originally to be queued on the microtask queue rather than a task queue.

When an algorithm requires a microtask to be queued, it must be appended to the relevant event loop's microtask queue; the task source of such a microtask is the microtask task source. 典型会产生microtask的有Promise、MutationObserver、Process.nextTick

Event Loop的执行流程

An event loop must continually run through the following steps for as long as it exists:

  1. Let oldestTask be 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 oldestTask.

  3. Run oldestTask.

  4. Set the event loop's currently running task back to null.

  5. Remove oldestTask from its task queue.

  6. Microtasks: Perform a microtask checkpoint.

  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. [HRT]

    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:

    • Any Document B that is nested through a Document A must be listed after A in the list.

    • If there are two documents A and B whose browsing contexts are both nested browsing contexts and their browsing context containers are both elements in the same Document C, then the order of A and B in the list must match the relative tree order of their respective browsing context containers in C.

    In the steps below that iterate over docs, each Document must be processed in the order it is found in the list.

    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.

    Note: Whether a top-level browsing context would benefit from having its rendering updated depends on various factors, such as the update frequency. For example, if the browser is attempting to achieve a 60Hz refresh rate, then these steps are only necessary every 60th of a second (about 16.7ms). If the browser finds that a top-level browsing context is not able to sustain this rate, it might drop to a more sustainable 30Hz for that set of Documents, rather than occasionally dropping frames. (This specification does not mandate any particular model for when to update the rendering.) Similarly, if a top-level browsing context is in the background, the user agent might decide to drop that page to a much slower 4Hz, or even less.

    Another example of why a browser might skip updating the rendering is to ensure certain tasks are executed immediately after each other, with only microtask checkpoints interleaved (and without, e.g., animation frame callbacks interleaved). For example, a user agent might wish to coalesce timer callbacks together, with no intermediate rendering updates.

    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.

    Note As with top-level browsing contexts, a variety of factors can influence whether it is profitable for a browser to update the rendering of nested browsing contexts. For example, a user agent might wish to spend less resources rendering third-party content, especially if it is not currently visible to the user or if resources are constrained. In such cases, the browser could decide to update the rendering for such content infrequently or never.

    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, update animations and send events for that Document, passing in now as the timestamp. [WEBANIMATIONS]

    7.9 For each fully active Document in docs, run the fullscreen 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.

贴张图来配合HTML5标准理解,顿时让人神清气爽。 68747470733a2f2f706963312e7a68696d672e636f6d2f76322d61643161323531636239316433373632353138356134666238373434393466635f622e706e67

简单的说就是:task是下一个事件循环执行的,microtask是在当前循环task执行完之后执行的。

如何知道是task还是mircotask

Testing is one way. See when logs appear relative to promises & setTimeout, although you're relying on the implementation to be correct.

The certain way, is to look up the spec.

试一下就行,虽然这个依赖于浏览器的正确实现,还有就是文档

console.log('current task start'); 

//setImmediate(function(){ console.log('setImmediate'); },0); 

setTimeout(function(){ console.log('setTimeout'); },0); 

setInterval(function(){ console.log('setInterval'); },0); 

new Promise(function(resolve){ 
console.log('Promise start'); resolve(); console.log('Promise end'); 
}).then(function(){ console.log('Promise.then'); }); 

//process.nextTick(function(){ console.log('process.nextTick'); });

var channel = new MessageChannel();
channel.port1.onmessage = function(){console.log('MessageChannel')};
channel.port2.postMessage(1);

var observer = new MutationObserver(function(){console.log('MutationObserver')});
var textNode = document.createTextNode('1');
observer.observe(textNode, {
                characterData: true
            });
textNode.data = '2';

console.log('current task end'); 

setImmediate目前只有IE支持,process.nextTick是在Node环境才有。

chrome 63.0.3239.108下的结果,注意其中打印的undefined,它是task和microtask的分界线。 image

IE11 edge下的结果(setInterval由于不方便截图没有记录) image

Safari 11.0.2下的结果 image

参考

tkaabbc commented 5 years ago

nice!

webjohnjiang commented 5 years ago

题主请问undefined为什么是macro和micro的分割线,是怎么做到的?

masterkong commented 5 years ago

题主请问undefined为什么是macro和micro的分割线,是怎么做到的?

这是浏览器的特性

webjohnjiang commented 5 years ago

题主请问undefined为什么是macro和micro的分割线,是怎么做到的?

这是浏览器的特性

这个特性有具体的资料吗,以前没听过这个