event loop(事件循环/事件轮询)
背景
- JS是单线程运行的
- 异步要基于回调来实现
- event loop就是异步回调的实现原理
JS从上到下一行一行执行,如果某一行执行报错,则停止下面代码的执行。先执行同步代码,再执行异步代码。
过程
同步代码,一行一行地放在call stack执行
- 遇到异步代码,会先“记录”下,等待时机(定时、网络请求等)
- 时间到了,就移动到callback queue中
- 如果同步代码执行完了,call stack为空的时候,event loop开始工作
- 轮询查找callback queue,如果有就把callback queue中的回调函数移动到call stack中让浏览器执行
- 继续轮询查找,直到calllback queue中为空
-
DOM事件和event loop
- 注意:绑定事件(黄色部分)是同步代码,是跟着Hi之后执行的,异步的部分是回调函数(红色部分)
-
promise进阶
promise的三种状态
resolved(fulfilled)、rejected、pending
- pending可以变成resloved或者rejected,并且变化不可逆
- pending状态不会触发then和catch
- 状态变为 resolved 会触发后续的 then 回调
-
then和catch改变状态
then正常返回resolved,里面有报错则返回rejected
- catch正常返回resolved,里面有报错则返回rejected
- 相关面试题 常考链式调用 ```javascript // 第一题 Promise.resolve().then(() => { console.log(1) }).catch(() => { console.log(2) }).then(() => { console.log(3) }) // 1 3
// 第二题 Promise.resolve().then(() => { // 返回 rejected 状态的 promise console.log(1) throw new Error(‘erro1’) }).catch(() => { // 返回 resolved 状态的 promise console.log(2) }).then(() => { console.log(3) }) // 1 2 3 因为catch里面没有报错
// 第三题 Promise.resolve().then(() => { // 返回 rejected 状态的 promise console.log(1) throw new Error(‘erro1’) }).catch(() => { // 返回 resolved 状态的 promise console.log(2) }).catch(() => { console.log(3) }) // 1 2 因为resolved状态的promise不能被最后一个catch捕捉
<a name="e1drI"></a>
## async/await
<a name="dFNjP"></a>
### 和promise的关系
1. async/await是消灭异步回调的终极武器,但和promise并不互斥,两者相辅相成
2. 执行async函数,返回的是promise对象
3. await相当于promise的then
4. try...cath可捕获异常,代替了promise的catch
5. 代码演示
```javascript
async function fn1 () {
// return 100
return Promise.resolve(200)
}
/* const res1 = fn1()
res1.then(data => {
console.log('data', data); // 200
}) */
; (async function () {
const p1 = Promise.resolve(300)
const data = await p1
console.log('data', data); // 300
})()
; (async function () {
const data1 = await 400
console.log('data1', data1); // 400
})()
; (async function () {
const data2 = await fn1()
console.log('data2', data2); // 200
})()
; (async function () {
const p4 = Promise.reject('err')
// 相当于promise的catch
try {
const res = await p4
console.log(res); // err
} catch (e) {
console.error(e);
}
})()
异步的本质
- async/await是消灭异步回调的终极武器
- JS还是单线程,还得是有异步,还是得基于event loop
- async/await只是语法糖,但是很好用
- 面试题 ```javascript async function async1 () { // 第二步 console.log(‘async1 start’) // 立马执行async2 await async2() // 返回了一个undefined // await的后面都可以看做是callback里的内容,即异步。有点类似于定时器 // 第五步 开始执行异步代码 console.log(‘async1 end’) // 关键在这一步,它相当于放在 callback 中,最后执行 }
async function async2 () { // 第三步 console.log(‘async2’) }
// 第一步 console.log(‘script start’) // 到这步要立刻执行async1里的代码 async1() // 第四步 同步代码执行完毕 console.log(‘script end’)
/ script start async1 start async2 script end async1 end /
5. 变种面试题
```javascript
async function async1 () {
// 第二步
console.log('async1 start')
await async2()
// 下面三行都是异步回调
// 第五步
console.log('async1 end')
await async3()
// 下面一行是异步回调
// 第七步
console.log('async1 end 2');
}
async function async2 () {
// 第三步
console.log('async2')
}
async function async3 () {
// 第六步 同步代码执行完完毕
console.log('async3')
}
// 第一步
console.log('script start')
async1()
// 第四步 同步代码执行完毕
console.log('script end')
/* script start
async1 start
async2
script end
async1 end
async3
async1 end 2 */
微任务/宏任务
概念
- 宏任务 macroTask:setTimeout、setInterval、Ajax、DOM事件
- 微任务 microTask:Promise、async/await
-
event loop和DOM渲染
JS是单线程的,而且和DOM渲染共用一个线程
- JS执行的时候,得留一些时机供DOM渲染
每次call stack清空(即每次轮询结束),即同步任务执行完毕的时候。都是DOM重新渲染的机会,DOM机构如有改变则重新渲染。然后再去触发下一次event loop。
微任务和宏任务的区别
宏任务:DOM渲染后触发,如setTimeout
- 微任务:DOM渲染前触发,如Promise
- 宏任务是由浏览器规定的
- 微任务是ES6语法规定的
- 执行过程
相关面试题
- 描述event loop机制(可画图)
- 和DOM渲染关系
- 微任务和宏任务在event loop过程中的不同处理
- 宏任务和微任务的区别
- 宏任务 macroTask:setTimeout、setInterval、Ajax、DOM事件
- 微任务 microTask:Promise、async/await
- 宏任务在DOM渲染后触发
- 微任务在DOM渲染前触发
- Promise的三种状态,如何变化?
- pending resolved rejected
- pending可以变为resolved和rejected,
- 变化不可逆
- 第一题:1 3
- 第二题:1 2 3
- 第三题:1 2
- 第一题:a是一个promise,状态是resolved,值是100。b是promise返回的值,是100。
- 第二题:start 300 a 100 b 200。c是一个错误,阻断了代码执行,所以下面两行打印不出来,要用try…catch来接收。
- 100 400 300 200
- 因为Promise是微任务,setTimeout是宏任务。微任务先执行。
- 场景题 ```javascript async function async1 () { // 第二步 console.log(‘async1 start’); // 立即执行 await async2() // await之后的代码是回调 微任务1 // 第六步 console.log(‘async1 end’); }
async function async2 () { // 第三步 console.log(‘async2’); }
// 第一步 console.log(‘script start’);
// 宏任务1 // 第八步 setTimeout(function () { console.log(‘setTimeout’); }, 0)
// 立即执行 async1()
// 初始化promise时传入的函数会立即执行 new Promise(function (resolve) { // 第四步 console.log(‘promise1’); resolve() }).then(function () { // 微任务2 // 第七步 // 微任务执行完毕 尝试触发DOM渲染,但是这里没有DOM改变,触发event loop机制,开始执行宏任务 console.log(‘promise2’); })
// 第五步 // 同步代码执行完毕 开始执行微任务 console.log(‘script end’);
/ script start async1 start async2 promise1 script end async1 end promise2 setTimeout / ```
- 手写promise