前言
我们知道 JavaScript 是单线程语言,也就是同一时间只能做一件事,这是因为 JavaScript 的主要作用是与用户互动以及操作 DOM ,如果是多线程的话,就会带来复杂的同步问题,比如:两个线程同时操作一个 DOM 节点,一个线程修改该节点,另一个线程删除该节点,此时就不知道该以哪个线程为准了。
但是单线程,又意味着任务需要排队等待,前一个任务执行完之后,后一个任务才会执行,这样就会导致一些执行很慢的任务会耽误大量的时间,于是 JavaScript 把所有的任务分成两种:同步任务、异步任务
同步任务和异步任务
- 同步任务,指的是,在主线程上排队执行的任务,只有前一个任务执行完毕之后,后一个任务才会开始执行。
- 异步任务,指的是,不进入主线程,而是进入任务队列,如果有多个任务,会在任务队列中排队等待,只有主线程的任务执行完毕之后,才会依次执行任务队列中的任务。
在了解同步任务异步任务的运行机制之前,我们先了解一下 执行栈、Event Table 以及 Event Queue 这几个名词
执行栈
我们知道,所有同步任务都在主线程执行,这样就形成来一个执行栈,也就是说,执行栈是一个存储函数调用的栈结构,主要负责跟踪所有要执行的代码
Event Table
Event Table 可以理解为是一张事件表格,存储的是异步任务中的事件以及其对应的回调函数列表,当异步任务有了结果,就会被丢到 Event Queue 中
Event Queue
Event Queue 就是所说的任务队列,当 Event Table 中的事件触发之后其对应的回调函数会被 push 到这个任务队列中,等待主线程任务完成后执行栈
那么,我们来看下其主要运行机制吧:
- JavaScript 代码执行时,将同步任务放入执行栈中,按顺序执行
- 异步任务进入 Event Table 注册,当指定的事件完成时,Event Table 会将函数回调放入任务队列中,等待执行
- 主线程任务执行完毕,会去 Event Queue 中取出完成的异步任务的回调放入主线程去执行
- 不断重复上述过程,直至所有任务全部执行完毕,也就是我们所说的事件循环
到此,我们就了解了 JavaScript 中的同步任务和异步任务,但是我们的异步任务又可以分为两种:宏任务和微任务
宏任务和微任务
- 宏任务:script (整体代码)、setTimeout 、 setInterval 、 I/O (Mouse Events、Keyboard Events)、UI交互事件
- 微任务:Promise.then (非 new Promise)、 process.nextTick、
宏任务和微任务的区别在于队列中事件执行的优先级,微任务的优先级高于宏任务,也就是说,进入整体代码后,执行任务,当执行栈清空之后,会先查看有没有微任务,有微任务的话,会执行完所有的微任务之后,在执行一次宏任务,执行完之后,会再次检查有没有微任务,如此反复,直至所有任务都执行完毕。
由此我们可以简单的总结出来这个执行顺序:
- 同步任务执行
- 同步任务执行完毕,执行所有的微任务
- 读取任务队列中的一个宏任务执行
- 执行完宏任务之后,执行所有的微任务
- 再读取宏任务执行
- ……如此反复,直至代码执行完毕
那么了解了这些之后,我们来看一道题:
1 | console.log('1'); |
这里就不详细讲解这道题了,相信上面所说的都理解之后,这道题就很简单了。这里直接放出答案:1,7,6,8,2,4,3,5,9,11,10,12