event loop(事件循环/事件轮询)

  1. JS是单线程运行的
  2. 异步要基于回调来实现
  3. event loop 就是异步回调的实现机制

JS执行过程:从前到后,一行一行执行;如果某一行执行报错,则停止下面代码的执行;先把同步代码执行完;再执行异步。

event loop的执行过程(极其重要!)

image.png
image.png
(异步)示例如下:
image.png

  1. 将console.log(“Hi”)推入调用栈,调用栈会执行代码执行代码,控制台打印“Hi”,调用栈清空
  2. 执行setTimeout,setTimeout由浏览器定义,不是ES6的内容;将定时器到Web APIs中,到时间后将回调函数放到回调函数队列中
  3. 执行完了setTimeout,清空调用栈
  4. console.log(“Bye”)进入调用栈,执行,调用栈清空
  5. 同步代码被执行完,,回调栈空,浏览器内核启动时间循环机制五秒之后,定时器将cb1推到回调函数队列中
  6. 事件循环将cb1放入调用栈

(DOM事件)示例如下:
image.png

  1. 将console.log(“Hi”)推入调用栈,调用栈会执行代码执行代码,控制台打印“Hi”,调用栈清空
  2. 执行click,click由浏览器定义,不是ES6的内容;将click到Web APIs中,当有用户点击时将回调函数放到回调函数队列中
  3. 执行完了click,清空调用栈
  4. console.log(“Bye”)进入调用栈,执行,调用栈清空
  5. 同步代码被执行完,,回调栈空,浏览器内核启动时间循环机制,click将回调函数推到回调函数队列中
  6. 事件循环将回调函数放入调用栈
  7. 打印“button clicked”,调用栈清空

DOM事件和event loop

  1. JS是单线程的
  2. 异步(setTimeout,ajax等)使用回调,基于event loop
  3. DOM事件也是用回调,基于event loop (DOM事件不是异步)

    promise有哪三种状态

    三种状态

  • 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的面试题

    1. Promise.resolve().then(() => {
    2. console.log(1) //1
    3. }).catch(() => {
    4. console.log(2)
    5. }).then(() => {
    6. console.log(3)//3
    7. })
  1. 先打印“1”
  2. 因为.then里如果不报错,就会返回一个resolve状态的promise
  3. 所以执行.then,打印“3”

    1. Promise.resolve().then(() => {
    2. console.log(1)
    3. throw new Error('erro1')
    4. }).catch(() => {
    5. console.log(2)
    6. }).then(() => {
    7. console.log(3)
    8. })
  4. 1

  5. 2
  6. 3 因为catch不报错会返回一个resolve状态的promise

    1. Promise.resolve().then(() => {
    2. console.log(1)
    3. throw new Error('erro1')
    4. }).catch(() => {
    5. console.log(2)
    6. }).catch(() => { // 注意这里是 catch
    7. console.log(3)
    8. })
  7. 1

  8. 2

    async/await语法介绍

    为了解决异步回调地狱,我们引入了Promise then catch链式调用,但也是基于回调函数的。async/await是同步语法,可以彻底消灭回调函数。
    示例如下,将promise加载图片使用async/await进行改进,用同步的方式编写异步 ```javascript function loadImg(src) { const promise = new Promise((resolve, reject) => {
    1. const img = document.createElement('img')
    2. img.onload = () => {
    3. resolve(img)
    4. }
    5. img.onerror = () => {
    6. reject(new Error(`图片加载失败 ${src}`))
    7. }
    8. img.src = src
    }) return promise }

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) } })()

  1. <a name="Z9RtL"></a>
  2. ## async/await和Promise的关系
  3. - async/await是消火异步回调的终极武器
  4. - 但和Promise并不互斥
  5. - 反而,两者相辅相成
  6. **总结来看,async 封装 Promise,await 处理 Promise 成功(相当于then),try...catch 处理 Promise 失败。**_**(极其重要!!!)**_
  7. <a name="vJ97p"></a>
  8. ### 执行async 函数,返回的是Promise 对象
  9. ```javascript
  10. async function fn2() {
  11. return new Promise(() => {})//返回promise对象
  12. }
  13. console.log( fn2() )
  14. async function fn1() {
  15. return 100 //返回值
  16. }
  17. 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) // 不会执行 })()

  1. <a name="ccDPP"></a>
  2. ### try...catch 可捕获异常,代替了Promise的catch
  3. ```javascript
  4. (async function () {
  5. const p4 = Promise.reject('some err')
  6. try {
  7. const res = await p4
  8. console.log(res)//此条语句会被执行
  9. } catch (ex) {
  10. console.error(ex)
  11. }
  12. })()

async/await是语法糖,异步的本质还是回调函数

async/await是消灭异步回调的终极武器
JS还是单线程,还得是有异步,还得是基于event loop
async/await只是一个语法糖,但这颗糖真香!
image.png
image.png
await 是同步写法,但本质还是异步调用。即,只要遇到了 await ,后面的代码都相当于放在 callback 里。

for…of

  1. for in 、 forEach、for是常规的同步遍历
  2. for of 常用于异步的遍历

同步遍历的forEach不会等待异步的返回结果,用async也不会等待。经过尝试for in和for是支持异步,forEach不支持。

  1. // 定时算乘法
  2. function multi(num) {
  3. return new Promise((resolve) => {
  4. setTimeout(() => {
  5. resolve(num * num)
  6. }, 1000)
  7. })
  8. }
  9. // // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的
  10. //因为它是一个同步遍历。同步遍历不会等待任何东西。它会一遍一遍执行。
  11. //一瞬间将遍历数字执行muti进行了三遍。然后1秒后一起打印出来。
  12. // function test1 () {
  13. // const nums = [1, 2, 3];
  14. // nums.forEach(async x => {
  15. // const res = await multi(x);
  16. // console.log(res);
  17. // })
  18. // }
  19. // test1();
  20. // 使用 for...of ,可以让计算挨个串行执行
  21. async function test2 () {
  22. const nums = [1, 2, 3];
  23. for (let x of nums) {
  24. // 在 for...of 循环体的内部,遇到 await 会挨个串行计算
  25. const res = await multi(x)
  26. console.log(res)
  27. }
  28. }
  29. test2()

什么是宏任务和微任务

  • 宏任务: setTimeout ,setInterval,Ajax,DOM事件
  • 微任务:Promise (对于前端来说),async/await
  • 微任务比宏任务执行的更早

    1. console.log(100)
    2. setTimeout(() => {
    3. console.log(200)
    4. })
    5. Promise.resolve().then(() => {
    6. console.log(300)
    7. })
    8. console.log(400)
    9. // 100 400 300 200

    event loop和DOM渲染

  • JS是单线程的,而且和DOM渲染共用一个线程

  • JS执行的时候,得留一些时机供DOM渲染

再次回顾 event loop 的过程:

  1. 每一次 call stack 结束(即第一次同步代码全部执行完),都会触发 DOM 渲染(不一定非得渲染,就是给一次 DOM 渲染的机会!!!)
  2. 然后再进行 event loop(cb1放进call Stack执行完之后Call Stack又空闲了,再次尝试DOM渲染)

image.png

  1. const $p1 = $('<p>一段文字</p>')
  2. const $p2 = $('<p>一段文字</p>')
  3. const $p3 = $('<p>一段文字</p>')
  4. $('#container')
  5. .append($p1)
  6. .append($p2)
  7. .append($p3)
  8. console.log('length', $('#container').children().length )
  9. alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染')
  10. // (alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果)
  11. // 到此,即本次 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 })

  1. <a name="w4e7k"></a>
  2. ## 微任务和宏任务的根本区别
  3. - 微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何干预,即可一次性处理完,更快更及时。
  4. - 宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。
  5. 微任务和宏任务在 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)
  6. <a name="xMsOJ"></a>
  7. ## 面试题
  8. <a name="Mex3a"></a>
  9. ### 描述 event loop机制(可画图)
  10. ![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)
  11. <a name="Ible3"></a>
  12. ### 什么是宏任务和微任务,两者的区别是什么?
  13. ![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)
  14. <a name="tzPVn"></a>
  15. ### promise的三种状态,如何变化
  16. ![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)
  17. <a name="XuIM3"></a>
  18. ### 场景题-promise then和catch的连接
  19. ![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)
  20. <a name="t4LxN"></a>
  21. ### 场景题-async/await语法
  22. ![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)
  23. <a name="I9VLP"></a>
  24. ### 场景题-promise和setTimeout的顺序
  25. ![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)
  26. <a name="j9hYn"></a>
  27. ### 场景题-外加async/await的顺序问题
  28. ```javascript
  29. async function async1 () {
  30. console.log('async1 start')//2
  31. await async2()
  32. //await后面都作为回调内容-微任务
  33. console.log('async1 end') // 6
  34. async function async2 () {
  35. console.log('async2')//3
  36. }
  37. console.log('script start')//1
  38. setTimeout(function () { // 宏任务 setTimeout
  39. console.log('setTimeout')//8
  40. }, 0)
  41. async1()
  42. //初始化promise时,传入的函数会立即执行
  43. new Promise (function (resolve) {
  44. console.log('promise1') // 4
  45. resolve()
  46. }).then (function () { // 微任务
  47. console.log('promise2')//7
  48. })
  49. console.log('script end')5
  50. // 同步代码执行完毕(event loop-call stack被清空)
  51. //执行微任务
  52. // 尝试触发DOM渲染
  53. // 触发 event loop,执行宏任务

手写promise-题目解读

image.png
image.png

手写promise-构造函数