JavaScript 的运行机制

JavaScript 的运行机制

事件循环 Event Loop

JavaScript 脚本加载完成时, JS 引擎会去预解析JS代码, 为代码中的「对象」预先在堆内存中分配地址空间, 然后按

顺序逐句解释执行( 即时编译 JIT)

第一次JS代码执行时, 会默认创建一个「全局执行上下文」, 并将其压入「执行栈」底, 然后每当引擎遇到一个函数

调用时, 都将会为其创建一个的「函数执行上下文」, 并将其压入执行栈顶. 引擎会执行那些执行上下文位于栈顶

的函数。当该函数执行结束时,「执行上下文」从栈中弹出,控制流程到达当前栈中的下一个上下文。

每一个 JavaScript 程序都有且只有一个默认的「全局执行上下文」, 在浏览器环境中它指向 Window

exceute_stack

macro-task(宏任务):包括整体代码script(同步宏任务),setTimeout、setInterval(异步宏任务)

micro-task(微任务):Promise,process.nextTick,ajax请求(异步微任务)

当引擎在创建函数「执行上下文」之前, 会判断当前函数是 同步任务还是 异步任务, 如果是 同步任务, 就进入主

线程创建「函数执行上下文」并且压入 执行栈顶等待执行, 如果是 异步任务 , 则不 进入主线程执行 , 不创建函数

执行上下文 , 而是被Event Table 所记录, 当 异步任务准备好时, 为其注册回调用函数, 进入事件队列Event

Quene等待执行

当执行栈执行完毕时, 调用 事件队列 Event Quene 中的回调函数进栈执行, 在异步任务Event Table 所记录之

前, 会判断是 「宏任务」 还是 「微任务」 , 分别被 宏任务 Event Table 和 微任务Event Table 记录, 最后再注册回

调函数, 并进入相应的「宏任务事件队列 」和 「微任务事件队列」

注意「宏任务」和「微任务」并不是严格意义上的 “平级关系”

在主线程调取「任务队列」的 回调函数进入「执行栈」执行时, 优先调用「微任务事件队列」执行完毕 再调用

「宏任务事件队列」 以上步骤循环,就是 「JavaScript」 的 事件循环

talk is cheap show your code, 用代码解释:

console.log('1');
// 记作 set1
setTimeout(function () {
    console.log('2');
    // set4
    setTimeout(function () {
        console.log('3');
    });
    // pro2
    new Promise(function (resolve) {
        console.log('4');
        resolve();
    }).then(function () {
        console.log('5');
    });
});

// 记作 pro1
new Promise(function (resolve) {
    console.log('6');
    resolve();
}).then(function () {
    console.log('7');
    // set3
    setTimeout(function () {
        console.log('8');
    });
});

// 记作 set2
setTimeout(function () {
    console.log('9');
    // 记作 pro3
    new Promise(function (resolve) {
        console.log('10');
        resolve();
    }).then(function () {
        console.log('11');
    });
});
  1. 整体代码 script 为一个大的宏任务,进入执行栈执行, 顺序执行 打印1

  2. 遇到定时器set1为异步的宏任务不进入主线程执行,注册进入宏任务队列 (set1)

  3. 继续执行遇到 Promise pro1 先执行其中的同步代码 打印6 , pro1中的then()为异步的微任务不进入主线程执行, 注册进入微任务队列 (pro1),

  4. 继续执行遇到定时器 set2 为异步的宏任务不进入主线程执行, 注册进入宏任务队列 (set1 set2),

  5. 此时执行栈空,检测任务队列是否有任务等待执行,优先执行微任务 (pro1)

  6. 执行微任务队列 (pro1) pro1的 then() 打印7

    1. 继续执行遇到定时器set3 不进入主线程执行,注册进入宏任务队列 (set1 set2 set3)
  7. 微任务队列空, 执行宏任务队列 (set1 set2 set3) set1 打印2 ,

    1. 遇到定时器set4为异步的宏任务不进入主线程执行,注册进入宏任务队列 (set2 set3 set4)
    2. 遇到 Promise pro2 先执行其中的同步任务,并且为其then 注册微任务队列 (pro2)
  8. 执行栈继续调用任务队列回调函数执行, 此时微任务队列 (pro2)不为空, pro2 的then()优先执行打印5

  9. 执行微任务完毕, 继续执行宏任务队列(set2 set3 set4) set2 打印9

    1. 遇到 pro3 同样先执行其中同步代码打印10再为其then 注册微任务队列 (pro3)
  10. 执行栈继续调用任务队列回调函数执行, 优先执行微任务队列(pro3) pro3的 then() 打印11 ,

  11. 微任务队列空, 执行宏任务队列(set3 set4)set3 打印8 执行set4 打印3

//打印顺序:1 6 7 2 5 9 10 11 8 3

以上就是JS中的 「事件循环」Event Loop

执行顺序为 同步(宏)任务->异步微任务->异步宏任务->…

微任务(microtask)

ES6新引入了Promise标准,同时浏览器实现上多了一个microtask微任务概念,在ECMAScript中,microtask也被称为jobs

我们已经知道宏任务结束后,会执行渲染,然后执行下一个宏任务, 而微任务可以理解成在当前宏任务执行后立即执行的任务

当一个宏任务执行完,会在渲染前,将执行期间所产生的所有微任务都执行完

宏任务 -> 微任务 -> GUI渲染 -> 宏任务 -> ...

图解:

js_eventloop

执行栈

当 JavaScript 脚本加载完成, 第一次被JavaScript 引擎所读取时, 会默认创建一个「全局执行上下文」, 并将其压入执行栈底, 然后每当引擎遇到一个函数调用, 它都会为该函数创建一个新的「函数执行上下文」并压入栈的顶部。

引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。

每一个 JavaScript 程序都有且只有一个默认的「全局执行上下文」, 在浏览器环境中它指向 Window

下面是一段模拟执行栈顺序

代码:

let a = 'Hello World!';

function first() {
  console.log('Inside first function');
  second();
  console.log('Again inside first function');
}

function second() {
  console.log('Inside second function');
}

first();
console.log('Inside Global Execution Context');

打印台:

//Inside first function
//Inside second function
//Again inside first function
//Inside Global Execution Context

图解执行栈:

exceute_stack


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!