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

背景

  1. JS是单线程运行的
  2. 异步要基于回调来实现
  3. event loop就是异步回调的实现原理
  4. JS从上到下一行一行执行,如果某一行执行报错,则停止下面代码的执行。先执行同步代码,再执行异步代码。

    过程

  5. 同步代码,一行一行地放在call stack执行

  6. 遇到异步代码,会先“记录”下,等待时机(定时、网络请求等)
  7. 时间到了,就移动到callback queue中
  8. 如果同步代码执行完了,call stack为空的时候,event loop开始工作
  9. 轮询查找callback queue,如果有就把callback queue中的回调函数移动到call stack中让浏览器执行
  10. 继续轮询查找,直到calllback queue中为空
  11. image.png

    DOM事件和event loop

  12. image.png

  13. 注意:绑定事件(黄色部分)是同步代码,是跟着Hi之后执行的,异步的部分是回调函数(红色部分)
  14. DOM事件也使用回调,基于event loop实现

    promise进阶

    promise的三种状态

  15. resolved(fulfilled)、rejected、pending

  16. pending可以变成resloved或者rejected,并且变化不可逆
  17. pending状态不会触发then和catch
  18. 状态变为 resolved 会触发后续的 then 回调
  19. 状态变为 rejected 会触发后续的 catch 回调

    then和catch改变状态

  20. then正常返回resolved,里面有报错则返回rejected

  21. catch正常返回resolved,里面有报错则返回rejected
  22. 相关面试题 常考链式调用 ```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捕捉

  1. <a name="e1drI"></a>
  2. ## async/await
  3. <a name="dFNjP"></a>
  4. ### 和promise的关系
  5. 1. async/await是消灭异步回调的终极武器,但和promise并不互斥,两者相辅相成
  6. 2. 执行async函数,返回的是promise对象
  7. 3. await相当于promise的then
  8. 4. try...cath可捕获异常,代替了promise的catch
  9. 5. 代码演示
  10. ```javascript
  11. async function fn1 () {
  12. // return 100
  13. return Promise.resolve(200)
  14. }
  15. /* const res1 = fn1()
  16. res1.then(data => {
  17. console.log('data', data); // 200
  18. }) */
  19. ; (async function () {
  20. const p1 = Promise.resolve(300)
  21. const data = await p1
  22. console.log('data', data); // 300
  23. })()
  24. ; (async function () {
  25. const data1 = await 400
  26. console.log('data1', data1); // 400
  27. })()
  28. ; (async function () {
  29. const data2 = await fn1()
  30. console.log('data2', data2); // 200
  31. })()
  32. ; (async function () {
  33. const p4 = Promise.reject('err')
  34. // 相当于promise的catch
  35. try {
  36. const res = await p4
  37. console.log(res); // err
  38. } catch (e) {
  39. console.error(e);
  40. }
  41. })()

异步的本质

  1. async/await是消灭异步回调的终极武器
  2. JS还是单线程,还得是有异步,还是得基于event loop
  3. async/await只是语法糖,但是很好用
  4. 面试题 ```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 /

  1. 5. 变种面试题
  2. ```javascript
  3. async function async1 () {
  4. // 第二步
  5. console.log('async1 start')
  6. await async2()
  7. // 下面三行都是异步回调
  8. // 第五步
  9. console.log('async1 end')
  10. await async3()
  11. // 下面一行是异步回调
  12. // 第七步
  13. console.log('async1 end 2');
  14. }
  15. async function async2 () {
  16. // 第三步
  17. console.log('async2')
  18. }
  19. async function async3 () {
  20. // 第六步 同步代码执行完完毕
  21. console.log('async3')
  22. }
  23. // 第一步
  24. console.log('script start')
  25. async1()
  26. // 第四步 同步代码执行完毕
  27. console.log('script end')
  28. /* script start
  29. async1 start
  30. async2
  31. script end
  32. async1 end
  33. async3
  34. async1 end 2 */

微任务/宏任务

概念

  1. 宏任务 macroTask:setTimeout、setInterval、Ajax、DOM事件
  2. 微任务 microTask:Promise、async/await
  3. 微任务执行时机比宏任务要早

    event loop和DOM渲染

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

  5. JS执行的时候,得留一些时机供DOM渲染
  6. 每次call stack清空(即每次轮询结束),即同步任务执行完毕的时候。都是DOM重新渲染的机会,DOM机构如有改变则重新渲染。然后再去触发下一次event loop。

    微任务和宏任务的区别

  7. 宏任务:DOM渲染后触发,如setTimeout

  8. 微任务:DOM渲染前触发,如Promise
  9. 宏任务是由浏览器规定的
  10. 微任务是ES6语法规定的
  11. 执行过程

image.png

相关面试题

  1. 描述event loop机制(可画图)
    1. 和DOM渲染关系
    2. 微任务和宏任务在event loop过程中的不同处理
  2. 宏任务和微任务的区别
    1. 宏任务 macroTask:setTimeout、setInterval、Ajax、DOM事件
    2. 微任务 microTask:Promise、async/await
    3. 宏任务在DOM渲染后触发
    4. 微任务在DOM渲染前触发
  3. Promise的三种状态,如何变化?
    1. pending resolved rejected
    2. pending可以变为resolved和rejected,
    3. 变化不可逆
  4. image.png
    1. 第一题:1 3
    2. 第二题:1 2 3
    3. 第三题:1 2
  5. image.png
    1. 第一题:a是一个promise,状态是resolved,值是100。b是promise返回的值,是100。
    2. 第二题:start 300 a 100 b 200。c是一个错误,阻断了代码执行,所以下面两行打印不出来,要用try…catch来接收。
  6. image.png
    1. 100 400 300 200
    2. 因为Promise是微任务,setTimeout是宏任务。微任务先执行。
  7. 场景题 ```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 / ```

  1. 手写promise