在我们的日常开发中或者面试,我们都会遇到这样的问题:给定的几行代码,我们需要知道其输出内容和顺序。因为javascript是一门单线程语言,所以我们可以得出结论: javascript是按照语句出现的顺序执行的
看到这里是不是想打人,难道我还不知道js是一行一行执行的?不急不急,请把你的40米长刀收起来
我们以为的js是这样的:
const A = '我是第一个输出';
console.log(A);
const B = '我是第二个输出';
console.log(B);
多么舒服,然鹅现实js是这样的:
console.log('我是第一个');
setTimeout(function(){
console.log('我是第二个');
});
new Promise(function(resolve){
console.log('我是第三个');
resolve();
}).then(function(){
console.log('我是第四个');
});
console.log('我是第五个');
依照js是一行一行执行,理想中的结果是:
//"我是第一个"
//"我是第二个"
//"我是第三个"
//"我是第四个"
//"我是第五个"
然鹅实际上的输出结果是:
//'我是第一个'
//'我是第三个'
//'我是第五个'
//'我是第四个'
//'我是第二个'
接下来,我们来谈谈javascript的执行机制
js是一门单线程的语言,所有的任务都是需要一个一个按顺序去执行,而任务又分为两类:
- 同步任务
- 异步任务
- 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数。
- 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
- 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
- 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
还是上面那段代码,提取一部分:
new Promise(function(resolve){
console.log('我是第一个');
resolve();
}).then(function(){
console.log('我是第二个');
});
console.log('我是第三个');
遇到Promise进入Event Table立即执行并注册函数then,then函数分发到Event Queue
遇到console.log()
,立即执行。
主线程内的任务执行完毕,去Event Queue读取对应的函数then,进入主线程执行
输出的结果:
//'我是第一个'
//'我是第三个'
//'我是第二个'
对于同步任务和异步任务,我们还有更精细的定义:
- 宏任务:整体代码script,setTimeout,setInterval
- 微任务:Promise,process.nextTick
console.log('我是第一个');
setTimeout(function(){
console.log('我是第二个');
new Promise(function(resolve) {
console.log('我是第三个');
resolve();
}).then(function() {
console.log('我是第四个')
})
});
new Promise(function(resolve){
console.log('我是第五个');
resolve();
}).then(function(){
console.log('我是第六个');
}).then(function(){
console.log('我是第七个');
});
console.log('我是第八个');
- 代码作为宏任务进入主线程
- 遇到console.log(‘我是第一个’),立即执行
- 遇到setTimeout,将其回调函数注册后分发到宏任务Event Queue队列中。
- 遇到了new Promise立即执行console.log(‘我是第五个’),then函数分发到微任务Event Queue队列中
- 遇到console.log(‘我是第八个’),立即执行
- 第一个宏任务执行结束,看看微任务队列中有没有任务,发现有then,执行 console.log(‘我是第六个’),then函数分发到微任务队列中
- 第二轮循环,发现微任务队列中的then,执行 console.log(‘我是第七个’)
- 第三轮循环,发现没有了微任务,主线任务完成,进入第一个setTimeout宏任务Event Queue执行回调函数,遇到了 console.log(‘我是第二个’),执行
- 遇到了new Promise立即执行console.log(‘我是第三个’),then函数分发到微任务Event Queue
- 第四轮循环,发现宏任务setTimeout回调函数中还有微任务then,执行console.log(‘我是第四个’)
- 完毕
输出结果:
我是第一个
我是第五个
我是第八个
我是第六个
我是第七个
我是第二个
我是第三个
我是第四个
再来一个复杂一点的
setTimeout(function() {
console.log('2');
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
setTimeout(function(){
console.log('5');
})
console.log('6');
}).then(() => {
console.log('7');
}).then(()=>{
console.log('8');
})
setTimeout(function(){
console.log('9');
})
})
- 进入宏任务setTimeout,执行回调,console.log(‘2’)
- 遇到new Promise,执行console.log(‘4’),then发放到微任务队列中
- 遇到第一个setTimeout,发放到宏任务队列中
- 第二次循环,发现微任务then,执行回调,发现第二个setTimeout,发放到宏任务队列中,执行console.log(‘6’),then发放到微任务中
- 第三次循环,发现微任务then,执行console.log(‘7’),then发放到微任务队列中
- 第四次循环,发现微任务then,执行console.log(‘8’)
- 第五次循环,微任务队列中没有任务,执行第一个setTimeout回调,console.log(‘9’)
- 第六次循环,微任务队列中没有任务,执行第二个setTimeout回调,console.log(‘5’)
- 完毕
可以再继续尝试:
setTimeout(function() {
console.log('2');
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
setTimeout(function(){
console.log('5');
})
console.log('6')
}).then(() => {
console.log('7')
}).then(()=>{
console.log('8')
})
setTimeout(function(){
console.log('9');
})
new Promise(function(resolve) {
console.log('10');
resolve();
}).then(function() {
setTimeout(function(){
console.log('11');
})
console.log('12')
}).then(() => {
console.log('13')
}).then(()=>{
console.log('14')
})
})
手动滑稽
本文参考:https://juejin.im/post/59e85eebf265da430d571f89
转载请标明出处,谢谢~