个人理解,如有不对,欢迎提出!
众所周知,JS是单线程脚本语言,单线程就意味着JS在执行的时候只有一个主线程来处理。主要特点是非阻塞,JS引擎就是通过事件循环机制来实现这一点的。而浏览器环境与nodejs下的事件循环机制有许多不同,本章主要来讲浏览器环境下js引擎的事件循环机制。
事件循环:( 个人理解 ),当同步任务执行完成后执行栈为空,系统会到任务队列中读取对应的任务,推入主线程,上述过程的不断重复称为事件循环。
从上面的这段话中最直观的理解到异步任务会被添加任务队列中稍后执行。
可能看了上面的我个人的理解会有点看不懂,那么接下来上代码
- 同步任务:可立即执行的任务(在执行栈中执行)。
- 异步任务:不可立即执行的任务(会进入任务队列中等待),异步任务包括宏任务和微任务(这个后面会讲到)。
- 执行栈:可以想象成一个容器,任务会到执行栈中去执行。
- 主线程:就像操作员,负责执行栈中的任务。
- 任务队列:等待被执行的任务。
在代码中更好理解:
console.log('1')
setTimeout(function() {
console.log('setTimeout1');
}, 0);
setTimeout(function() {
console.log('setTimeout2');
}, 0);
console.log('2');
输出结果为:1, 2,promise1,setTimeout1, setTimeout2
解析:
- 首先执行同步任务,console.log(‘1’) 入栈,输出1,出栈
- 接着遇到第一个setTimeout,我们把它称为定时器1,定时器1入栈,判断是异步任务,出栈交给异步处理模块处理,
- 遇到第二个setTimeout, 我们把它称为定时器2,定时器2入栈,判断是异步任务,出栈交给异步处理模块处理
- 再执行同步任务,console.log(‘2’); 入栈 ,输出2,出栈
- 这时同步任务已经全部执行完,此时执行栈为空,会接着读取任务队列里面的异步事件,定时器1的回调函数入栈,输出 setTimeout1 ,出栈,
- 定时器2 的回调函数入栈,输出setTimeout2,出栈
此时遇到一个问题,读取任务队列中的事件顺序是什么样的呢, 任务队列是一个先进先出的数据结构,排在最前面的会优先被主线程读取,所以现在就应该知道为什么是定时器1 先入栈,定时器2后入栈了。
宏任务和微任务
异步任务分为宏任务和微任务,宏任务队列可以有多个,微任务队列只能有一个。
宏任务
script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering
微任务
process.nextTick, Promise.then(), Object.observe, MutationObserver
当异步任务交给异步处理模块处理完打到触发条件时,根据任务类型,将回调函数压入任务队列。此时如果是宏任务,则新增一个宏任务队列;如果是微任务,则直接压入微任务队列中。在代码执行中先执行微任务,待微任务执行完后再执行宏任务。
什么都不说了,直接上代码
console.log('1');
setTimeout(function() {
console.log('setTimeout1');
}, 0);
setTimeout(function() {
console.log('setTimeout2');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
})
Promise.resolve().then(function(){
console.log(4);
})
Promise.resolve().then(function(){
console.log(42);
})
console.log('2');
输出结果: 1,2, promise1,4,42,setTimeout1,setTimeout2
解析:
- 首先执行同步任务,console.log(‘1’) 入栈,输出1,出栈;
- 第一个定时器( 称为定时器1 )入栈,判断是异步任务,出栈,交给异步处理模块处理后直接加入宏任务队列中;
- 第二个定时器( 称为定时器2 )入栈,判断是异步任务,出栈,交给异步处理模块处理后直接加入宏任务队列中;
- 第一个Promise( 称为Promise1 )入栈,判断是异步任务,出栈,交给异步处理模块处理后直接加入微任务队列中;
- 第二个Promise( 称为Promise2 )入栈,判断是异步任务,出栈,交给异步处理模块处理后直接加入微任务队列中;
- 第三个Promise( 称为Promise3 )入栈,判断是异步任务,出栈,交给异步处理模块处理后直接加入微任务队列中;
- console.log(‘2’)入栈,输出2,出栈。
- 先读取微任务队列,Promise1入栈,输出 promise1,出栈;
- Promise2入栈,输出 4,出栈;
- Promise3入栈,输出 42,出栈;
- 当微任队列为空后读取宏任务
- 定时器入栈,输出 setTimeout1,出栈
- 定时器2入栈,输出 setTimeout2,出栈
当在执行宏任务中遇到微任务,则执行完该宏任务后先去读取微任务,待微任务队列为空后再去读取下一个宏任务
setTimeout(function(){
console.log(1);
Promise.resolve().then(function(){
console.log(2);
});
console.log(6);
},0 );
setTimeout(function(){
console.log(3);
}, 0);
输出结果:1,6,2,3
解析:
- 在执行第一个定时器时,先输出1,然后遇到异步Promise, 加入微任务队列,然后输出 6;
- 执行微任务,输出2
- 此时微任务队列为空,读取下一个宏任务,输出3
Promise.resolve().then(() => {
console.log(0);
}).then(() => {
console.log(1)
})
Promise.resolve().then(() => {
console.log(2);
}).then(() => {
console.log(3);
})
结果: 0,2,1,3
参考1 : https://mp.weixin.qq.com/s/BmHXjPzzeL7OJO4q_tBF4g
参考2 :https://www.cnblogs.com/cangqinglang/p/8967268.html