首先我们看一个例子:
console.log("a");
setTimeout(()=>{
console.log("b");
},0);
console.log("c");
Promise.resolve().then(()=>{
console.log("d");
}).then(()=>{
console.log("e");
});
console.log("f");
结果:acfdeb
JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
Js先执行同步任务,遇到异步任务先放入任务队列,同步任务执行完后从任务队列里面取出异步任务。放到主线程执行
事件循环:主线程从任务队列中读取事件的过程是循环不断的,所以称为事件循环
事件循环执行过程:
同步和异步任务分别进入不同的执行“场所”,同步进入主线程,异步进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,回去了Event Queue读取对应的函数,进入主线程。
异步任务
- 宏任务(macrotask):每次执行栈执栈的代码(包括每次从事件队列中获取一个事件回调并放在执行栈中执行),包括主代码块(同步)、setTimeout、setInterval(异步)等
- 微任务(microtask):微任务,在task执行结束后立即执行的任务,包括promise的then、process.nextTick(微任务队列,优先级更高)等
运行机制:
先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。
这么看来。文章开头的例子就很清楚明了了。
再来一个例子:
setTimeout(() => {
new Promise(resolve => {
resolve();
}).then(() => {
console.log('test');
});
console.log(4);
});
new Promise(resolve => {
resolve();
console.log(1)
}).then(() => {
console.log(3);
Promise.resolve().then(() => {
console.log('before timeout');
}).then(() => {
Promise.resolve().then(() => {
console.log('also before timeout')
})
})
})
console.log(2);
输出结果为:
- 遇到setTimeout,异步宏任务,将() => {console.log(4)}放入宏任务队列中;
- 遇到new Promise,new Promise在实例化的过程中所执行的代码都是同步进行的,所以输出1;
- 而Promise.then中注册的回调才是异步执行的,将其放入微任务队列中
- 遇到同步任务console.log(2),输出2;主线程中同步任务执行完
- 从微任务队列中取出任务到主线程中,输出3,此微任务中又有微任务,Promise.resolve().then(微任务a).then(微任务b),将其依次放入微任务队列中;
- 从微任务队列中取出任务a到主线程中,输出 before timeout;
- 从微任务队列中取出任务b到主线程中,任务b又注册了一个微任务c,放入微任务队列中;
- 从微任务队列中取出任务c到主线程中,输出 also before timeout;微任务队列为空
- 从宏任务队列中取出任务到主线程,此任务中注册了一个微任务d,将其放入微任务队列中,接下来遇到输出4,宏任务队列为空