浏览器工作原理
宏任务和微任务
对于script标签中的所有同步代码,整体可以看成一个宏任务。
宏任务,macrotask,也叫tasks。 一些==异步任务的回调==会依次进入macro task queue,等待后续被调用,这些异步任务包括:
-
setTimeout
-
setInterval
-
setImmediate (Node独有)
-
requestAnimationFrame (浏览器独有)
-
I/O
-
UI rendering (浏览器独有)
微任务,microtask,也叫jobs。 另一些==异步任务的回调==会依次进入micro task queue,等待后续被调用,这些异步任务包括:
- process.nextTick (Node独有)
- Promise.then()
- Object.observe
- MutationObserver (注:这里只针对浏览器和NodeJS)
注意:Promise构造函数里的代码是同步执行的,而Promise.then()是异步的。
浏览器端的 eventloop
组成:一个函数执行栈stack、一个事件队列Task Queue(可看作宏任务队列)、一个微任务队列Microtask Queue。
一轮事件循环:每次从事件队列中取出一个事件(每次从宏任务队列中取出一个宏任务)放入函数执行栈执行,执行完毕后紧接着把此时微任务事件队列中的微任务事件一个一个全部取出并执行。
然后,在取出宏任务事件队列中的下一个宏任务并执行,执行完毕后紧接着把此时微任务事件队列中的微任务事件一个一个全部取出并执行。.......如此往复循环。
例题
来个基础题加深理解:
例1:
setTimeout(()=> {
console.log(1)
Promise.resolve(3).then(data => console.log(data))
}, 0)
setTimeout(()=> {
console.log(2)
}, 0)
首先,第1行的setTimeout函数是宏任务1,第5行的setTimeout函数是宏任务2。第3行的promise.then()函数是微任务1。
执行过程为:
宏任务1(第一个setTimeout函数)进入函数执行栈并执行,输出1,宏任务1执行完毕执行此时微任务队列中的微任务1进入函数执行栈并执行,输出3;接着,宏任务2进入函数执行栈并执行,输出2 。所以,输出顺序为 1 ,3 ,2。
小tip: 这个例子中
第一轮事件循环:将宏任务0(script函数)放入函数执行栈执行,此时微任务事件队列为空;(若在浏览器控制台写代码,没有返回值时,浏览器会自动返回一个返回值,此返回值与插件相关,不用深究)
第二轮事件循环:宏任务1(第一个setTimeout函数)进入函数执行栈并执行,输出1,宏任务1执行完毕执行此时微任务队列中的微任务1(Promise.then()函数)进入函数执行栈并执行,输出3;
第三轮事件循环:宏任务2(第二个setTimeout函数)进入函数执行栈并执行,输出2,此时微任务事件队列为空;
例2:
console.log("script start")
setTimeout(function () {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function () {
console.log('promise1');
}).then(function () {
console.log('promise2');
});
// script start
// promise1
// promise2
// setTimeout