简介

JS运行的环境称之为宿主环境。

执行栈

执行栈:call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。

JS引擎永远执行的是执行栈的最顶部。

异步函数

异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制。

浏览器宿主环境中包含5个线程:

  1. JS引擎:负责执行执行栈的最顶部代码
  2. GUI线程:负责渲染页面
  3. 事件监听线程:负责监听各种事件
  4. 计时线程:负责计时
  5. 网络线程:负责网络通信

当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。

JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

微任务与宏任务

事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:

  • 宏任务【macro task】
    • script(整体代码)
    • 事件回调
    • 计时器结束的回调 setTimeout setInterval
    • requestAnimationFrame
    • setImmediate I/O UI rendering
    • http回调
    • 等等绝大部分异步函数进入宏队列
  • 微任务【micro task】
    • Promise
    • process.nextTick Object.observe MutationObserver

MutationObserver用于监听某个DOM对象的变化

注意:

  1. Promise 的异步体现在 thencatch ,所以 new Promise 中的代码会立即执行
  2. async 函数也会立即执行,不会造成阻塞,遇到 await 会执行 await 代码,人后将 await 后面的代码加入到微任务中,跳出当前 async 函数( await 后面的代码相当于 Promise.then 中的代码)

事件循环 Event Loop

调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入执行栈中执行。任务队列分为宏任务和微任务,当主线程空闲了,会优先取微任务队列中的任务进行执行,直到微任务队列清空,再去宏任务队列中取宏任务进行执行。然后再看是否有微任,如果有就执行微任务,没有就继续执行下一个宏任务。如此循环,就形成了事件循环,第一次事件循环中,js引擎会把整个 script 代码当成一个宏任务执行。(总之每次执行完一个宏任务就会去看看是否有微任务)

练习

  1. 输出结果:247536async2的结果1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    setTimeout(function(){  //1、执行异步:放入宏任务队列
    console.log("1");
    },0);

    async function async1() {
    console.log("2"); //3、在执行过程中,这里是同步代码,直接执行
    const data = await async2(); //4、遇到await,执行await后面的代码,即执行async2函数;
    //await会返回一个promise,await下面的代码会在async2函数成功后将其放入微任务队列
    console.log("3"); //8、async2执行成功,添加进微任务队列
    return data;
    };

    async function async2() {
    return new Promise((resolve) => {
    console.log("4"); //5、这里是同步执行代码
    resolve("async2的结果"); //6、将当前promise函数状态置位成功;
    }).then((data) => {
    console.log("5"); //7、添加进微任务队列
    return data;
    });
    };

    async1().then((data) => { //2、执行async1函数
    console.log("6"); //9、async1执行成功,添加进微任务队列
    console.log(data);
    });

    new Promise(function (resolve) {
    console.log("7"); //同步执行
    }).then(function(){
    console.log("8");
    });

关于promise.then 的回调函数是在什么时候进入微任务队列的

通俗易懂:原文地址

  1. then 是在 resolve 之前被调用的

    thenresolve 之前,then 不会加微任务,而是缓存起来,resolve 看到缓存里又 then 的回调,于是加微任务。

  2. then 是在 resolve 之后被调用的

    resolvethen 之前,resolve 的时候还没有任何回调要执行,自然不会加微任务。then 的时候发现已经 fullfilled ,于是直接加微任务。

源码级:原文地址
结论:调用 then 方法时 Promise 状态为 fulfilled,回调函数 onFulfilled 直接加入 microtask 队列;调用 then 方法时 Promise 状态为 pending,首先将 onFulfilled 加入一个链表,待 Promise 状态变为 fulfilled 后,将链表中的每一项加入 microtask 队列。

__END__