众所周知,JavaScript是一门单线程语言,等到上一个任务结束之后才会进行下一个任务。但是如果上一个任务迟迟没有结束,那下一个任务就会卡在原地无法动弹。
这时,我们就将所有的任务分为两类:同步任务和异步任务。
什么是同步和异步?
- 同步:后一个任务等待前一个任务结束后才会开始执行。
- 异步:后一个任务无需等待前一个任务结束就可以执行。
为什么会出现异步任务?
- JS是单线程语言,同一时间只能做一件事,为了提高性能,在执行过程中不阻塞后续代码的执行。通常会采用异步操作。
如何执行同步异步?
(1)同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
(2)所有同步任务都在主线程上排队依次执行,形成一个执行栈。
(3)主线程之外,还存在一个"任务队列"(task queue)。异步任务进入Event Table并注册函数,异步任务有了运行结果,就在“任务队列”之中放置一个事件。
(4)一旦主线程中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入主线程执行。
(5)主线程内的任务执行完毕为空,就会从"任务队列"中读取事件,这个过程是循环不断的,形成event loop(事件循环)。
以上也就是JS的初步执行机制(事件循环)。
那怎么才能判断出主线程的执行栈是否为空呢?
答:js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
什么是宏任务和微任务?
一般情况,我们将异步任务又分为宏任务和微任务。
- 宏任务:包含整个script代码块,setTimeout, setIntval、setImmediate、Ajax、DOM事件。
- 微任务:Promise.then catch finally , process.nextTick。
以上内容纯八股文,记住就好。
虽说宏任务与微任务都是异步任务,那它们的执行顺序也是一样的吗?
显而易见,答案是不一样。
事实是,微任务的执行时间比宏任务要早!
所以我们在执行完同步任务之后开始执行异步任务的顺序应该是:
- 每个宏任务队列里面有微任务队列,执行宏任务的过程中遇到微任务,就把微任务加入到微任务队列中;
- 当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行;
- 微任务执行完毕后再将异步宏任务从队列中调入主线程执行。
- 一直循环直至所有任务执行完毕。
通俗地讲,则是以下顺序:
执行同步代码 ==> 检查微任务并执行 ==> 执行宏任务1 ==> 检查微任务并执行 ==> 执行宏任务2 ==> 检查微任务并执行 ==> 执行宏任务3 …
这算是更精确的JS执行机制。
找个很经典的例题看一下吧。
setTimeout(function(){
console.log('1')
});
new Promise(function(resolve){
console.log('2');
resolve();
}).then(function(){
console.log('3')
});
console.log('4');
执行结果: 2-4-3-1
- 这段代码作为宏任务,进入主线程。
- 先遇到setTimeout,那么将其回调函数注册后分发到宏任务event queue.
- 接下来遇到Promise, new Promise立即执行,then函数分发到微任务event queue
- 遇到console.log(), 立即执行
- 整体代码script作为第一个宏任务执行结束, 查看当前有没有可执行的微任务,执行then的回调。(第一轮事件循环结束了,我们开始第二轮循环)
- 从宏任务的event queue开始,我们发现了宏任务event queue中setTimeout对应的回调函数,立即执行。