JavaScript基础自检之EventLoop

556 阅读5分钟

这个记录(或说笔记),参照一名【合格】前端工程师的自检清单这篇文章,根据里面所提的某些问题,进行归纳和回答。

  1. JavaScript基础自检之原型和原型链
  2. JavaScript基础自检之变量和类型
  3. JavaScript基础自检之作用域和闭包

EventLoop

这部分来自stackoveflow一个问题的回答。我感觉总结的很好,我做了下简单的翻译,拿来和大家一起分享:

EventLoop是JavaScript中异步模型的基础,主要作用是执行代码、收集和处理事件以及执行子任务。事件循环过程的模型如下:

当前正在调用的栈区为空时,执行下面的步骤:

  1. 在任务队列中选择最早的任务(任务A)

  2. 如果任务A为null(意味着任务队列为空),则执行步骤6

  3. 将"当前正在执行的任务"设置为任务A

  4. 执行任务A(意味着执行回调函数)

  5. 将"当前正在执行的任务"设置为null,并从任务队列中移除A

  6. 执行微任务队列

    • (a)在微任务队列中选择最早的任务(task x)

    • (b)如果任务x是null,则接着步骤(g)

    • (c)将"当前正在执行任务"设置为"task x"

    • (d)运行任务x

    • (e)将"当前正在执行任务"设置为null,并移除"task x"

    • (f)在微任务队列中,选择下一个 ”最早任务“,并接着从(b)开始

    • (g)结束微任务队列

  7. 继续执行步骤1

注意:

  1. 当一个任务(在宏任务队列中)正在执行时,可能会注册新的事件,因此可能会创建新的任务。下面的两种方式将会创建新的任务:

    • promiseA.then() 的回调是一个任务

      • promiseA是完成状态(resolved/rejected),该任务将在当前事件循环中被推送到微任务队列

      • promiseA是pending状态,该任务将在未来的事件循环中(可能是下一轮),被推入微任务队列

    • setTimeout(callback,n)的callback是一个任务,并且将会推入宏任务队列。

  2. 微任务队列中任务将在本轮执行,而宏任务队列中的任务必须等待下一轮事件循环

  3. click,scroll,ajax,setTimeout的回调是任务,脚本代码在 script tag 中 作为一个整体,也是一个任务(宏任务)。

分析下面代码的输出并解释为什么

Promise.resolve()
                .then(() => { // 标记为 1-1
                    console.log('孙婧-1');
                    Promise.reject().then(() => {
                         // 这个代码不会走到这里的
                         }, () => { // 标记为 2-1
                         console.log('孙婧-2');
                    })
                })
                .then(() => { // 标记为 1-2
                    console.log('李运华-1');                
                })
                
// 上段代码等价于

var a = Promise.resolve()
                .then(() => { // 1-1
                    console.log('孙婧-1');
                    Promise.reject().then(() => {
                        // 这个代码不会走到这里的
                        }, () => { // 标记为 2-1
                           console.log('孙婧-2');
                    })
                })
a.then(() => { // 1-2
    console.log('李运华-1');
})

// 输出:
'孙静-1'
'孙婧-2'
'李运华-1'

下面是对这段代码的分析:

由于Promise.resolve()返回一个fulfilled的Promise,所以执行「1-1」then方法后,「1-1」then方法回调被推入「本轮事件循环」的微任务队列;「1-1」方法执行之后,返回的是一个pending状态的Promise(大家可以打个断点看看),那么「1-2」then方法执行之后,它的回调被推入「下轮事件循环」的微任务队列。

此时,任务队列(宏任务队列)已经没有别的任务,那么开始执行本轮的微任务队列,首先执行的是「1-1」then方法的回调:(1)执行log方法输出“孙静-1”(2)遇到fulfilled的Promise,执行「2-1」then方法,并将其回调加入本轮事件循环的微任务队列,至此「1-1」then的回调执行结束。微任务队列中剩下 「2-1」,弹出执行,输出“孙静-2”;至此,本轮事件循环结束。

开始执行下一轮的事件循环,「1-2」then方法回调被推入微任务队列,由于没有别的任务执行,并且此时微任务队列中只有「1-2」then方法的回调,弹出执行,输出“李运华-1”。至此,执行结束。

为何try里面放return,finally还会执行,理解其内部机制

function foo() {
    try {
        return console.log('1111');
    }catch(error) {
        console.log(`error: ${error}`);
    }finally {
        console.log('finally');
    }
}

var value = foo();
console.log(value);

// 输出:
'1111'
'finally'
undefined

分析下上述代码:

由上面代码的输出结果,先输出的是「1111」,也就是说return 后面的语句已经执行,但是并没有返回结果,因为如果返回了结果,那么必然不会输出「finally」。那么由此可以得出:return 会先执行后面的表达式,并将结果存储,待到「finally块」执行完毕,再返回之前存储的结果。(有错误望大家指出来)

JavaScript如何实现异步编程,可以详细描述EventLoop机制

实现异步编程的方式

  • 回调函数

    回调函数,是实现JavaScript异步编程最基础的方式,我们平常的ajax请求以及一些事件的处理,采用的都是这种方式。

  • Promise

    Promise是一个容器,可以存储将来发生的任务。

    function promiseTest() {
        return new Promise((resolve, reject) => {
            setTimeout(resolve, 1000, '孙婧');
        });
    }
    
    promiseTest().then((res) => {
        console.log('res:', res);
    })
    
    // 1秒后(预计)输出
    res: 孙婧
    

    上面我们定义了一个方法promiseTest:返回一个Promise实例。实例中存储了一个定时器,返回一个字符串「孙婧」。

  • 异步函数

    异步函数是ES8中增加的规范,用于简化异步编程。它内部的实现,是对Promise的封装。详细信息,可以去MDN的[async function](developer.mozilla.org/zh-CN/docs/…去了解。

宏任务和微任务分别有哪些?

宏任务:setTimeout, setInterval, I/O, requireAnimationFrame, UI rendering等

微任务:process.nextTick, Promise, queueMicrotask, MultationObserver等

使用Promise实现串行

我这里就列一种方法(有错误,请提出来😊):

  1. 使用async/await

    async function execPromiseQueue(array) { // array 是 待执行的Promise数组
      console.log(array);
      for(let i = 0; i < array.length; i++) {
         try {
            var result = await Promise.resolve(array[i]);    
            console.log('result', result);
         } catch(error) {
            console.log('error', error)
         }
      }
    }
    
    var arr = [
       Promise.resolve('1212'),
       Promise.reject('12'),
    ]
    
    execPromiseQueue(arr);