event loop(事件循环/事件轮询)
- JS是单线程运行的
- 异步要基于回调来实现
- event loop 就是异步回调的实现机制
JS执行过程:从前到后,一行一行执行;如果某一行执行报错,则停止下面代码的执行;先把同步代码执行完;再执行异步。
event loop的执行过程(极其重要!)
(异步)示例如下:
- 将console.log(“Hi”)推入调用栈,调用栈会执行代码执行代码,控制台打印“Hi”,调用栈清空
- 执行setTimeout,setTimeout由浏览器定义,不是ES6的内容;将定时器到Web APIs中,到时间后将回调函数放到回调函数队列中
- 执行完了setTimeout,清空调用栈
- console.log(“Bye”)进入调用栈,执行,调用栈清空
- 同步代码被执行完,,回调栈空,浏览器内核启动时间循环机制五秒之后,定时器将cb1推到回调函数队列中
- 事件循环将cb1放入调用栈
(DOM事件)示例如下:
- 将console.log(“Hi”)推入调用栈,调用栈会执行代码执行代码,控制台打印“Hi”,调用栈清空
- 执行click,click由浏览器定义,不是ES6的内容;将click到Web APIs中,当有用户点击时将回调函数放到回调函数队列中
- 执行完了click,清空调用栈
- console.log(“Bye”)进入调用栈,执行,调用栈清空
- 同步代码被执行完,,回调栈空,浏览器内核启动时间循环机制,click将回调函数推到回调函数队列中
- 事件循环将回调函数放入调用栈
- 打印“button clicked”,调用栈清空
DOM事件和event loop
- pending :在过程中
- resolved :解决了
- rejected:失败了
状态的表现和变化
- pending状态:不会触发then或catch
- resolved状态:会触发后续的then回调函数
- rejected状态:会触发后续的catch回调函数
then 和 catch对状态的影响(重要!)
- then正常返回resolved,里面有报错则返回rejected(then其实也是一个新的promise,状态为pending)
- catch正常返回resolved,里面有报错则返回rejected(catch其实也是一个新的promise,状态为pending)
promise关于then和catch的面试题
Promise.resolve().then(() => {
console.log(1) //1
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)//3
})
- 先打印“1”
- 因为.then里如果不报错,就会返回一个resolve状态的promise
所以执行.then,打印“3”
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
1
- 2
3 因为catch不报错会返回一个resolve状态的promise
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).catch(() => { // 注意这里是 catch
console.log(3)
})
1
- 2
async/await语法介绍
为了解决异步回调地狱,我们引入了Promise then catch链式调用,但也是基于回调函数的。async/await是同步语法,可以彻底消灭回调函数。
示例如下,将promise加载图片使用async/await进行改进,用同步的方式编写异步 ```javascript function loadImg(src) { const promise = new Promise((resolve, reject) => {
}) return promise }const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
reject(new Error(`图片加载失败 ${src}`))
}
img.src = src
async function loadImg1() { const src1 = ‘http://www.imooc.com/static/img/index/logo_new.png‘ const img1 = await loadImg(src1) return img1 }
async function loadImg2() { const src2 = ‘https://avatars3.githubusercontent.com/u/9583120‘ const img2 = await loadImg(src2) return img2 }
(async function () { // 注意:await 必须放在 async 函数中,否则会报错 try { // 加载第一张图片 const img1 = await loadImg1() console.log(img1) // 加载第二张图片 const img2 = await loadImg2() console.log(img2) } catch (ex) { console.error(ex) } })()
<a name="Z9RtL"></a>
## async/await和Promise的关系
- async/await是消火异步回调的终极武器
- 但和Promise并不互斥
- 反而,两者相辅相成
**总结来看,async 封装 Promise,await 处理 Promise 成功(相当于then),try...catch 处理 Promise 失败。**_**(极其重要!!!)**_
<a name="vJ97p"></a>
### 执行async 函数,返回的是Promise 对象
```javascript
async function fn2() {
return new Promise(() => {})//返回promise对象
}
console.log( fn2() )
async function fn1() {
return 100 //返回值
}
console.log( fn1() ) // 相当于 Promise.resolve(100)
执行async会返回一个promise对象。如果返回的是个值会封装成promise返回。如果是promise对象,那么就返回此对象
await相当于Promise的then
- await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行 ```javascript (async function () { const p1 = new Promise(() => {}) await p1 console.log(‘p1’) // 不会执行 })()
(async function () { const p2 = Promise.resolve(100) const res = await p2 console.log(res) // 100 如果后面跟的是promise,await当做then使用 })()
(async function () {
const res = await 100
console.log(res) // 100 如果后面跟的是一个数,直接返回这个数
})()
(async function () { const p3 = Promise.reject(‘some err’) const res = await p3 console.log(res) // 不会执行 })()
<a name="ccDPP"></a>
### try...catch 可捕获异常,代替了Promise的catch
```javascript
(async function () {
const p4 = Promise.reject('some err')
try {
const res = await p4
console.log(res)//此条语句会被执行
} catch (ex) {
console.error(ex)
}
})()
async/await是语法糖,异步的本质还是回调函数
async/await是消灭异步回调的终极武器
JS还是单线程,还得是有异步,还得是基于event loop
async/await只是一个语法糖,但这颗糖真香!
await 是同步写法,但本质还是异步调用。即,只要遇到了 await ,后面的代码都相当于放在 callback 里。
for…of
- for in 、 forEach、for是常规的同步遍历
- for of 常用于异步的遍历
同步遍历的forEach不会等待异步的返回结果,用async也不会等待。经过尝试for in和for是支持异步,forEach不支持。
// 定时算乘法
function multi(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * num)
}, 1000)
})
}
// // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的
//因为它是一个同步遍历。同步遍历不会等待任何东西。它会一遍一遍执行。
//一瞬间将遍历数字执行muti进行了三遍。然后1秒后一起打印出来。
// function test1 () {
// const nums = [1, 2, 3];
// nums.forEach(async x => {
// const res = await multi(x);
// console.log(res);
// })
// }
// test1();
// 使用 for...of ,可以让计算挨个串行执行
async function test2 () {
const nums = [1, 2, 3];
for (let x of nums) {
// 在 for...of 循环体的内部,遇到 await 会挨个串行计算
const res = await multi(x)
console.log(res)
}
}
test2()
什么是宏任务和微任务
- 宏任务: setTimeout ,setInterval,Ajax,DOM事件
- 微任务:Promise (对于前端来说),async/await
微任务比宏任务执行的更早
console.log(100)
setTimeout(() => {
console.log(200)
})
Promise.resolve().then(() => {
console.log(300)
})
console.log(400)
// 100 400 300 200
event loop和DOM渲染
JS是单线程的,而且和DOM渲染共用一个线程
- JS执行的时候,得留一些时机供DOM渲染
再次回顾 event loop 的过程:
- 每一次 call stack 结束(即第一次同步代码全部执行完),都会触发 DOM 渲染(不一定非得渲染,就是给一次 DOM 渲染的机会!!!)
- 然后再进行 event loop(cb1放进call Stack执行完之后Call Stack又空闲了,再次尝试DOM渲染)
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
console.log('length', $('#container').children().length )
alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染')
// (alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果)
// 到此,即本次 call stack 结束后(同步任务都执行完了),浏览器会自动触发渲染,不用代码干预
为什么微任务比宏任务执行更早
- 宏任务:DOM渲染后触发,如setTimeout
- 微任务:DOM渲染前触发,如Promise
```javascript
// 修改 DOM
const $p1 = $(‘
一段文字
‘) const $p2 = $(‘一段文字
‘) const $p3 = $(‘一段文字
‘) $(‘#container’) .append($p1) .append($p2) .append($p3)
// // 微任务:渲染之前执行(DOM 结构已更新)
Promise.resolve().then(() => {
console.log = $(‘#container’).children().length
alert(micro task ${length}
)//DOM渲染了吗?—-NO
})
// 宏任务:渲染之后执行(DOM 结构已更新)
setTimeout(() => {
console.log(length2
,
$(#container
).children().length)
alert(setTimeout)//DOM渲染了吗?—-yes
})
<a name="w4e7k"></a>
## 微任务和宏任务的根本区别
- 微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何干预,即可一次性处理完,更快更及时。
- 宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。
微任务和宏任务在 event loop 会放在不同的地方等待<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22838722/1635161546562-88ea3edc-b12f-404d-bb18-c8b03e9b51a1.png#clientId=ue729783a-426e-4&from=paste&height=261&id=uaec64d7d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=521&originWidth=892&originalType=binary&ratio=1&size=104683&status=done&style=none&taskId=u39bf1cc3-cbc6-44c6-974d-bb75aa987d1&width=446)<br />则总体执行流程会发生变化,标准执行过程如下:![image.png](https://cdn.nlark.com/yuque/0/2021/png/22838722/1635161677114-29149ec4-e6dd-4e65-b20f-2426f6348cb4.png#clientId=ue729783a-426e-4&from=paste&height=251&id=u918f45b0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=501&originWidth=846&originalType=binary&ratio=1&size=86570&status=done&style=none&taskId=ua998d138-f0c8-4ec8-9ece-0e3357afc94&width=423)
<a name="xMsOJ"></a>
## 面试题
<a name="Mex3a"></a>
### 描述 event loop机制(可画图)
![image.png](https://cdn.nlark.com/yuque/0/2021/png/22838722/1635161981704-87944c73-5aaf-4ac9-adc6-08fc7160ba9d.png#clientId=uc210883e-b86e-4&from=paste&height=100&id=ua7190601&margin=%5Bobject%20Object%5D&name=image.png&originHeight=199&originWidth=584&originalType=binary&ratio=1&size=31354&status=done&style=none&taskId=ud10ebc2b-9c91-4c5a-9701-9c80b804b1d&width=292)
<a name="Ible3"></a>
### 什么是宏任务和微任务,两者的区别是什么?
![image.png](https://cdn.nlark.com/yuque/0/2021/png/22838722/1635162059324-9e085acb-bf3f-4fd9-ae99-b5b72233808a.png#clientId=uc210883e-b86e-4&from=paste&height=101&id=u3523f9db&margin=%5Bobject%20Object%5D&name=image.png&originHeight=202&originWidth=649&originalType=binary&ratio=1&size=32713&status=done&style=none&taskId=u855ed245-53e8-42b4-9b8d-3f63ceafa6e&width=324.5)
<a name="tzPVn"></a>
### promise的三种状态,如何变化
![image.png](https://cdn.nlark.com/yuque/0/2021/png/22838722/1635162121927-11dab2d8-8409-479b-a4be-8d9dbd30597e.png#clientId=uc210883e-b86e-4&from=paste&height=101&id=u9c72e9c4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=202&originWidth=594&originalType=binary&ratio=1&size=24703&status=done&style=none&taskId=u466833fe-5c68-4cf7-b6aa-d6d81d8ae95&width=297)
<a name="XuIM3"></a>
### 场景题-promise then和catch的连接
![image.png](https://cdn.nlark.com/yuque/0/2021/png/22838722/1635162410278-6b0327b1-e3a1-4037-8049-3fa491ab63dc.png#clientId=u0defed70-b7d1-4&from=paste&height=205&id=uf86c8e90&margin=%5Bobject%20Object%5D&name=image.png&originHeight=298&originWidth=869&originalType=binary&ratio=1&size=128064&status=done&style=none&taskId=u375172fb-e173-4412-ab78-7501a8cb369&width=597.5)
<a name="t4LxN"></a>
### 场景题-async/await语法
![image.png](https://cdn.nlark.com/yuque/0/2021/png/22838722/1635162635291-1628dc40-f4ab-4e49-bc87-a5d1e068f3fa.png#clientId=u0defed70-b7d1-4&from=paste&height=300&id=u78a382e8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=366&originWidth=847&originalType=binary&ratio=1&size=146170&status=done&style=none&taskId=u78ee7e42-1dae-460e-8242-e0e3a70bb3f&width=694.5)
<a name="I9VLP"></a>
### 场景题-promise和setTimeout的顺序
![image.png](https://cdn.nlark.com/yuque/0/2021/png/22838722/1635162894288-cb535854-a4c9-481d-a86c-7f4a2fbf8c99.png#clientId=u0defed70-b7d1-4&from=paste&height=195&id=u9a0bc48a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=390&originWidth=728&originalType=binary&ratio=1&size=115813&status=done&style=none&taskId=u234b39cb-9c12-4e40-9afb-e630904125a&width=364)
<a name="j9hYn"></a>
### 场景题-外加async/await的顺序问题
```javascript
async function async1 () {
console.log('async1 start')//2
await async2()
//await后面都作为回调内容-微任务
console.log('async1 end') // 6
async function async2 () {
console.log('async2')//3
}
console.log('script start')//1
setTimeout(function () { // 宏任务 setTimeout
console.log('setTimeout')//8
}, 0)
async1()
//初始化promise时,传入的函数会立即执行
new Promise (function (resolve) {
console.log('promise1') // 4
resolve()
}).then (function () { // 微任务
console.log('promise2')//7
})
console.log('script end')5
// 同步代码执行完毕(event loop-call stack被清空)
//执行微任务
// 尝试触发DOM渲染
// 触发 event loop,执行宏任务