JavaScript Event Loop

单线程的JavaScript

JavaScript 是单线程的,主要是因为 JavaScript 的主要用途是与用户交互,以及操作 DOM。如果 JavaScript 是多线程,那么当多个线程同时操作一个 DOM 元素时,那么就无法保证哪个线程优先去操作该 DOM 元素。

也许,有小伙伴会说:“我们可以通过HTML 5的 web worker 来创建 JavaScript 的多个线程啊”。是的,web worker 允许我们创建多个线程来执行 JavaScript,但创建的都是子线程,这些子线程都受到 JavaScript 唯一的主线程控制,而且这些子线程是不能够操作 DOM 元素的.

由于 JavaScript 的单线程机制,JavaScript代码是按照任务执行的,而任务分为同步任务和异步任务

同步任务:在主线程中执行,一个接着一个按顺序执行

异步任务:一开始就进入到一个任务队列中,主线程执行完当前任务后,就会去读取任务队列,某个异步任务可以开始了,该任务则会进入主线程执行

所以,JavaScript 执行的顺序是,先执行主线程上已有的任务,然后再读取“任务队列”,将“任务队列”中的任务加到主线程中继续执行。

任务队列,是一个事件队列,如果事件指定过回调函数,这些事件发生时就进入“任务队列”,等待主线程读取。我们常说的回调函数,也就是被主线程暂时挂起不执行的代码,当事件发生时,主线程才会将这代码加入进来执行。注意,同步任务总是在主线程读取“任务队列”之前执行的

事件循环(Event Loop)

主线程读取“任务列表”这一过程是循环不断的,这种运行机制称为事件循环。
事件循环

定时器

任务队列,不仅仅存放了异步任务的事件,还存放了定时事件(setTimeoutsetInterval
定时器 setTimeout 和 setInterval 只是把事件放置到任务队列中,必须等待任务队列中的事件执行完毕,主线程才会调用定时器相应的回调函数。

Microtasks 和 Macrotasks

任务队列不止一个,还包括了 microtasks(微任务) 和 macrotasks(宏任务)

microtasks

  • process.nextTick
  • promise
  • Object.observe
  • MutationObserver

macrotasks

  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI渲染
  • 一个事件循环中会有一个或多个任务队列
  • 任务队列就是宏任务(macrotasks)
  • 每个事件循环都有一个 microtasks queue
  • task queue == macrotasks queue != microtasks queue

主线程拥有一个执行栈和一个事件循环

因此,JavaScript 运行机制是这样的:

首先,全部代码的 script 是一个 macrotask。
第一次事件循环,主线程先执行同步任务(其实也是macrotask),执行过程中会创造新的macrotask,接着继续执行,然后会把一些事件,如promise(发现 then 方法,则放入 microtask),放置在 microtask 中。如果有 microtask 则执行 microtask,没有则进入下一次循环。

总结为:同步完成,一个宏任务,所有微任务,一个宏任务,所有微任务…

最后,看以下这道面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
console.log('start')
const interval = setInterval(() => {
console.log('setInterval')
}, 0)
setTimeout(() => {
console.log('setTimeout 1')
Promise.resolve()
.then(() => {
console.log('promise 3')
})
.then(() => {
console.log('promise 4')
})
.then(() => {
setTimeout(() => {
console.log('setTimeout 2')
Promise.resolve()
.then(() => {
console.log('promise 5')
})
.then(() => {
console.log('promise 6')
})
.then(() => {
clearInterval(interval)
})
}, 0)
})
}, 0)
Promise.resolve()
.then(() => {
console.log('promise 1')
})
.then(() => {
console.log('promise 2')
})

输出:

1
2
3
4
5
6
7
8
9
10
11
start
promise1
promise2
setInterval
setTimeout1
promise3
promise4
setInterval
setTimeout2
promise5
promise6
坚持原创技术分享,您的支持将鼓励我继续创作!