js是单线程的;js是单线程的;js是单线程的。重要的事情说三遍。
换而言之,在某个时间段内,js只会做一件事,只有当完成某件事时,才会去干下一件事。
看到这里有些看官已经把手举起来了:我难道不知道单线程是什么意思吗?骚年,别急,先把砖放下。单线程当然是一次只能干一件事,但是干完这件事,接下去先做哪件事呢?是按语句的先后顺序吗?那我们就来试试看:上代码
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');
看到这段代码,我会心一笑,拿起键盘,哒哒哒敲下:
script start
setTimeout
promise1
promise2
script end
自信满满,意气风发
骚年,等等。待老夫将这段代码输出验证一下,在装13不迟。输出结果:
script start
script end
promise1
promise2
setTimeout
页面在加载的时候,js是按语句顺序执行,但是遇到加载图片,加载音频这种耗时久的任务怎么办?难道等到加载完成在继续执行吗,万一网络情况不好,那么用户可能只能看到白屏了,原地爆炸。所以优秀的程序员将这些任务有分为了同步任务和异步任务。异步任务完成时,通知主线程,进而更新页面。
注意:Event Table中保存的是各种需要执行的异步任务,而Event Queue队列中是已经完成并注册的异步任务的回调函数。也就是说当一个异步任务在Event Queue中成功注册了回调函数,那么这个异步任务肯定是已经执行完毕了。
当主线程中执行玩同步任务时(此时JS引擎空闲),就会去读取Event Queue中的函数,进入主线程执行。js引擎中的monitoring process进程会持续去检查主线程是否空闲,如果空闲就会去Event Queue中检查是否有需要执行的函数。这整个循环就形成了Event Loop。
回到之前的题目,setTimeout,Promise这些都属于异步任务,那么怎么确定这些在Event Queue中的地位呢?这里又要引入一个概念:宏任务,微任务。
宏任务和微任务会进入对应的Event Queue,当执行一个宏任务时,会从头到尾执行完毕,期间不会执行微任务,只有当本次宏任务执行完毕后,才会执行所有的微任务;当微任务执行完毕,回去检查是否还有需要执行的宏任务,如此往复。
宏任务包括:主体script、setTimeout、setIntervalt、
微任务包括:Promise、process.nextTick
了解了这些,让我们回到最开始的那题。
第一步:运行主体script代码,从头到尾运行,所以先输出script start,遇到了属于宏任务的setTimeout,将它放入Event Queue;紧接着遇到了属于微任务的Promise,也放入Event Queue(与setTimeout不是同一个),执行最后一句,输出 script end;
第二步:执行完成了宏任务,紧接着执行所有的微任务,输出promise1、promise2;
第三步:检查是否还有未执行的宏任务,很显然还有一个setTimeout,输出setTimeout,结束。