JS原理 -事件循环 Event Loop
JS 中的事件循环及相关实例
定义
JS 在设计之初是被用来验证表单和操控DOM元素,如果js是多线程的,且两个线程同时对一个DOM元素进行了相互冲突的操作,那么浏览器的解析器是无法执行的,所以 JS 被设计为单线程。
Event Loop 即事件循环,是浏览器、Nodejs 避免因单线程运行出现阻塞的一种机制,也就是说通常说的异步。
宏队列和微队列
宏队列(macro task queue)以下异步任务的回调会依次进入宏队列,等待后续被调用:
- setTimeout
- setInterval
- I/O(文件读写)
- requestAnimationFrame (浏览器独有)
- UI rendering (浏览器独有)
微队列(micro task queue)以下异步任务的回调会依次进入微队列,等待后续被调用:
- Promise
- MutationObserver
- process.nextTick (Node独有)
- Object.observe(ES7废弃)
简要流程
事件循环简要的流程如下:
- 执行全局 script 代码,如果遇到宏任务,追加到宏队列中,如果遇到微任务,追加至微队列中。
- 全局代码执行完毕,调用栈清空。
- 检查微队列是否为空,如果不为空,依次取出微队列内的回调任务放入调用栈中,直到执行完成所有微任务,此时微队列为空,调用栈为空。
- 取出宏队列队首的任务,放入调用栈,执行完毕后,重复步骤 3 的操作,Event Loop 就形成了。
流程的重点就在于:每一次调用宏队列里的任务后,都需要去检查微队列是否为空,如果有微任务则执行微任务。
例子
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 代码,分配任务到队列。
宏队列:setTimeout callback
微队列:Promise then
Log:script start、script end
此时微队列不为空,需要处理微队列。
宏队列:setTimeout callback
微队列:
Log:script start、script end、promise1、promise2
微队列处理完成之后,此时微队列已空,然后再处理宏队列里的 setTimeout callback 任务。
宏队列:
微队列:
Log:script start、script end、promise1、promise2、setTimeout
复杂的例子
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
},1000)
setTimeout(() => {
console.log(7);
},100)
console.log(8);
事件循环详细过程:
执行全局 script 代码,因为 Promise 构造函数执行时立即调用,所以
console.log(4)
是同步的。宏队列:setTimeout callback、setTimeout callback、setTimeout callback
微队列:Promise then
Log:1、4、8
此时微队列不为空,需要处理微队列。
宏队列:setTimeout callback、setTimeout callback、setTimeout callback
微队列:
Log:1、4、8、5
处理 setTimeout callback,又因为 setTimeout 内包含了一个 Promise 函数,所以执行了 setTimeout 的 callback 时,微队列又添加了一个 Promise 的 callback。
宏队列:setTimeout callback、setTimeout callback、setTimeout callback
微队列:Promise then
Log:1、4、8、5、2
完成宏队列里的一个任务后,判断微队列不为空,暂停处理宏队列,再次优先处理微队列。
宏队列:setTimeout callback、setTimeout callback、setTimeout callback
微队列:
Log:1、4、8、5、2、3
依次处理剩下的宏队列任务。
Log:1、4、8、5、2、3、7、6
async 乱入
async 的特性:
async 定义的是一个 Promise 函数,和普通函数一样只要不调用就不会进入事件队列。
async function async1(){ console.log(1) await 1 console.log(2) } // 等价于 function async1() { console.log(1) return new Promise(resolve => { resolve(1) }).then(() => { console.log(2) }) } // 小试一下 async function async1 () { await new Promise((resolve, reject) => { resolve() }) console.log('A') } async1() new Promise((resolve) => { console.log('B') resolve() }).then(() => { console.log('C') }).then(() => { console.log('D') }) // 打印结果:B、A、C、D
async 内部如果没有主动 return Promise,那么 async 会把函数的返回值用 Promise 包装。
根据返回是否有
then
属性,是否是Promise
,Js引擎的处理方式也不一样:非 thenable、非 promise(不等待)
async function testA () { return 1; } testA().then(() => console.log(1)); Promise.resolve() .then(() => console.log(2)) .then(() => console.log(3)); // 输出结果:1/2/3
thenable(等待 1个
then
的时间)async function testB () { return { then (cb) { cb(); } }; } testB().then(() => console.log(1)); Promise.resolve() .then(() => console.log(2)) .then(() => console.log(3)); // 输出结果:2/1/3
Promise(等待 2个
then
的时间)async function testC () { return new Promise((resolve, reject) => { resolve() }) } testC().then(() => console.log(1)); Promise.resolve() .then(() => console.log(2)) .then(() => console.log(3)) .then(() => console.log(4)) // 输出结果:2/3/1/4
await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。即:遇到 await 关键字,await 右边的语句会被立即执行然后 await 下面的代码进入等待状态,等待 await 得到结果。
但是执行顺序和右侧函数的类型有关:
非 thenable、非 Promise(不等待)
async function test () { console.log(1); await 1; console.log(2); } test(); console.log(3); Promise.resolve() .then(() => console.log(4)) .then(() => console.log(5)) .then(() => console.log(6)) .then(() => console.log(7)); // 输出结果: 1/3/2/4/5/6/7
thenable(等待 1个
then
的时间)async function test () { console.log(1); await { then (cb) { cb(); }, }; console.log(2); } test(); console.log(3); Promise.resolve() .then(() => console.log(4)) .then(() => console.log(5)) .then(() => console.log(6)) .then(() => console.log(7)); // 输出结果: 1/3/4/2/5/6/7
Promise(不等待)
async function test () { console.log(1); await new Promise((resolve, reject) => { resolve() }) console.log(2); } test(); console.log(3); Promise.resolve() .then(() => console.log(4)) .then(() => console.log(5)) .then(() => console.log(6)) .then(() => console.log(7)); // 输出结果: 1/3/2/4/5/6/7 async function async1() { console.log('1') await new Promise((resolve) => { console.log('2') resolve() }).then(() => { console.log('3') }) console.log('4') } async1() // 输出结果: 1/2/3/4
来几个 async/await 的例子:
async function async1() {
console.log( '1' );
await async2();
console.log( '2' );
};
async function async2() {
console.log( '3' );
};
console.log( '4' );
setTimeout( function () {
console.log( '5' );
}, 0 );
async1();
new Promise( function ( resolve ) {
console.log( '6' );
resolve();
} ).then( function () {
console.log( '7' );
} );
console.log( '8' );
// 事件循环的详细顺序:
1. 执行同步代码 console.log。
Log:4
2. 将 setTimeout 添加至宏队列。
3. 执行 async1。
Log:4、1
4. 执行 async2,await 阻塞后续代码,先执行外部的代码,又因为 async 会返回一个 Promise,所以将返回的 Promise 添加至微队列。
Log:4、1、3
5. 执行 Promise,将新生成的 Promise 添加至微队列。
Log:4、1、3、6
6. 执行同步代码 console.log。
Log:4、1、3、6、8
7. 查看微队列,发现存在两个 Promise,按顺序执行
Log:4、1、3、6、8、2、7
8. 最后执行宏任务里的 setTimeout
Log:4、1、3、6、8、2、7、5
setTimeout(function () {
console.log('9')
}, 0)
console.log('1')
async function async1() {
console.log('2')
await async2()
console.log('8')
}
async function async2() {
return new Promise(function (resolve) {
console.log('3')
resolve()
}).then(function () {
console.log('6')
})
}
async1()
new Promise(function (resolve) {
console.log('4')
resolve()
}).then(function () {
console.log('7')
}).then(function () {
console.log('9')
})
console.log('5')
// 输出结果:1/2/3/4/5/6/7/8/9
// 重点:async2返回的是Promise,所以等待两个then(6/7)
async function async1() {
console.log('2')
const data = await async2()
console.log(data)
console.log('9')
}
async function async2() {
return new Promise(function (resolve) {
console.log('3')
resolve('8')
}).then(function (data) {
console.log('6')
return data
})
}
console.log('1')
setTimeout(function () {
console.log('10')
}, 0)
async1()
new Promise(function (resolve) {
console.log('4')
resolve()
}).then(function () {
console.log('7')
})
console.log('5')
// 输出结果:1/2/3/4/5/6/7/8/9/10
JS原理 -事件循环 Event Loop