关于浏览器Event Loop

最近看到Event Loop这个词出现的频率有点高,因而查阅各方资料在此记录一下。html

先不说概念,咱们来看段代码:react

console.log('script start');

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

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

复制这段代码到控制台,在Chrome会输出以下结果segmentfault

clipboard.png

Why?

若是想弄清楚缘由,就必须得弄清楚今天要提到的概念Event Loop。promise

运行时概念

clipboard.png

函数调用造成了一个栈帧。浏览器

function foo(b) {
  var a = 10;
  return a + b + 11;
}

function bar(x) {
  var y = 3;
  return foo(x * y);
}

console.log(bar(7)); // 返回 42

当调用 bar 时,建立了第一个帧 ,帧中包含了 bar 的参数和局部变量。当 bar 调用 foo 时,第二个帧就被建立,并被压到第一个帧之上,帧中包含了 foo 的参数和局部变量。当 foo 返回时,最上层的帧就被弹出栈(剩下 bar 函数的调用帧 )。当 bar 返回的时候,栈就空了。网络

对象被分配在一个堆中,即用以表示一大块非结构化的内存区域。多线程

队列

一个 JavaScript 运行时包含了一个待处理的消息队列。每个消息都关联着一个用以处理这个消息的函数。并发

在事件循环期间的某个时刻,运行时从最早进入队列的消息开始处理队列中的消息。为此,这个消息会被移出队列,并做为输入参数调用与之关联的函数。正如前面所提到的,调用一个函数老是会为其创造一个新的栈帧。app

函数的处理会一直进行到执行栈再次为空为止;而后事件循环将会处理队列中的下一个消息(若是还有的话)。异步

为何JavaScript是单线程

稍理解JavaScript的都知道JavaScript是单线程,即同一时间只能处理一件事情。JavaScript为何不能是多线程呢,这样就能够同时处理多件事情提升效率。
JavaScript的宿主最开始自己就是浏览器,处理用户的交互事件。做为浏览器脚本,它只能一次作一件事情,假如用户点击一个按钮的时候,须要删除一个节点,而另外一段代码此时又要添加这个节点,那JavaScript该如何处理,以谁为准?
因此JavaScript在创造之初就考虑到了这点,也决定了它只能是单线程,这是它的核心特征之一。

Event Loop

既然JavaScript是单线程的,那就意味着任务须要排队,只有前一个任务执行完毕,下一个任务才能开始,因而就有了任务队列。若是一个任务耗时很长,下面的任务就得一直等着,明显不太合理,那么可否先把耗时好久的任务先挂起来,先执行后面的任务,等IO设备返回的结果,再去执行以前挂着的任务。

因而任务就能够分两种:同步任务和异步任务

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务能够执行了,该任务才会进入主线程执行。

(1)全部同步任务都在主线程上执行,造成一个执行栈(execution context stack)。
(2)主线程以外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的全部同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,因而结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

异步任务指的是异步的代码加入到任务队列中,等待主线程通知执行

Event Loop

主线程从"任务队列"中读取事件,这个过程是循环不断的,因此整个的这种运行机制又称为Event Loop!

Event loop:客户端必须使用本章节中所描述的事件循环,来协调事件,用户交互,脚本,呈现,网络等等。 事件循环有两种:用于浏览上下文的事件循环和用于 worker 的事件循环。

bg2014100802.png

任务队列分为宏任务队列(macro tasks) 和 微任务队列(micro tasks)

如何判断一段代码是加入到宏任务队列仍是微任务队列?
每一个任务都由特殊任务源来定义。 来自同一个特殊任务源的全部任务都将发往特定事件循环。因此咱们能够按照不一样的来源进行分类,不一样来源的任务都对应到不一样的任务队列中

(macro-task 宏任务)来源:I/O, setTimeout + setInterval + setImmediate, UI renderder ···
(micro-task 微任务)来源:Promise ,process.nextTick ,MutationObserver, Object.observe ···
Microtasks are usually scheduled for things that should happen straight after the currently executing script, such as reacting to a batch of actions, or to make something async without taking the penalty of a whole new task. The microtask queue is processed after callbacks as long as no other JavaScript is mid-execution, and at the end of each task. Any additional microtasks queued during microtasks are added to the end of the queue and also processed. Microtasks include mutation observer callbacks, and as in the above example, promise callbacks.

看下完整的执行过程:

clipboard.png

• 代码开始执行,JavaScript 引擎对全部的代码进行区分。
• 同步代码被压入栈中,异步代码根据不一样来源加入到宏任务队列尾部,或者微任务队列的尾部。
• 等待栈中的代码被执行完毕,此时通知任务队列,执行位于队列首部的宏任务。
• 宏任务执行完毕,开始执行其关联的微任务。
• 关联的微任务执行完毕,继续执行下一个宏任务,直到任务队列中全部宏任务被执行完毕。
•执行下一个任务队列。

参考文档:

  1. 并发模型与事件循环
  2. JavaScript 运行机制详解:再谈Event Loop
  3. 什么是浏览器的事件循环(Event Loop)?
  4. 从 薛定谔的猫 聊到 Event loop
相关文章
相关标签/搜索