JS运行机制

32 阅读3分钟

1. 执行上下文(Execution Context)

执行上下文是代码运行时的环境,所有的 JavaScript 代码都是在执行上下文中执行的。它可以分为以下几种类型:

  • 全局执行上下文:JavaScript 程序开始运行时创建的执行上下文,只有一个。它是代码执行的最顶层环境。
  • 函数执行上下文:每当一个函数被调用时,都会为该函数创建一个新的执行上下文。
  • Eval 执行上下文:使用 eval() 执行的代码也会创建执行上下文。

执行上下文的生命周期:

  1. 创建阶段(Creation Phase) :在这个阶段,JavaScript 引擎会进行以下操作:

    • 创建 变量对象(Variable Object, VO)活动对象(Activation Object, AO) ,这取决于当前执行上下文是全局还是函数。
    • 为每个声明的变量和函数创建 标识符(Identifiers) ,并赋值为 undefined 或根据函数初始化。
  2. 执行阶段(Execution Phase) :执行代码,并根据上下文中定义的变量、函数和 this 来运行代码。

2. 调用栈(Call Stack)

调用栈是 JavaScript 引擎管理执行上下文的结构。当一个执行上下文被创建时,它会被推入调用栈。当代码执行完成时,执行上下文会被从调用栈中弹出。

  • 是一个后进先出(LIFO)的数据结构,最后加入栈中的元素会最先被移除。
  • JavaScript 引擎每次执行一个函数时,都会为该函数创建一个执行上下文,并将其推入栈顶;函数执行完后,执行上下文会从栈顶弹出。
function first() {
  second(); // 调用 second 函数
}

function second() {
  third(); // 调用 third 函数
}

function third() {
  console.log('Hello from third');
}

first(); // 从这里开始

调用栈执行顺序:

  1. first 函数被调用,first 的执行上下文推入栈。
  2. first 中调用 secondsecond 的执行上下文推入栈。
  3. second 中调用 thirdthird 的执行上下文推入栈。
  4. third 执行并输出 'Hello from third',然后 third 的执行上下文被弹出。
  5. 返回到 secondsecond 执行完毕后,second 的执行上下文被弹出。
  6. 返回到 firstfirst 执行完毕后,first 的执行上下文被弹出。

3. 事件队列(Event Queue)与异步任务

在 JavaScript 中,异步代码的执行通过事件循环机制(Event Loop)进行管理。JavaScript 运行时是单线程的,但是通过事件队列和异步任务,可以在主线程之外执行一些任务。

事件队列和异步任务:

  • 事件队列(Event Queue) :用来存储待执行的回调函数队列。
  • 异步任务:当异步操作(如 setTimeoutPromise、事件监听等)完成时,其对应的回调函数会被添加到事件队列中。

事件循环(Event Loop)

  1. 主线程:JavaScript 是单线程的,只有一个主线程来处理同步任务。
  2. 任务队列:当一个异步任务(例如定时器、setTimeout)完成时,它的回调函数会被添加到任务队列中,等待主线程空闲时执行。
  3. 事件循环:事件循环不断检查调用栈是否为空,若为空,则从任务队列中取出一个回调函数并执行。
console.log('Start');

setTimeout(function() {
  console.log('Timeout');
}, 0);

Promise.resolve().then(function() {
  console.log('Promise');
});

console.log('End');

输出顺序:

Start
End
Promise
Timeout

4. 微任务与宏任务

JavaScript 中的异步任务分为 宏任务(Macro Tasks)和 微任务(Micro Tasks):

  • 宏任务:包括 setTimeoutsetInterval、I/O 操作等。
  • 微任务:包括 Promise.thenMutationObserver 等。

执行顺序

  • 事件循环首先会执行所有同步代码。
  • 然后,微任务(比如 Promise)会被执行。
  • 最后,宏任务(比如 setTimeout)会被执行。
console.log('Start');

setTimeout(function() {
  console.log('Timeout');
}, 0);

Promise.resolve().then(function() {
  console.log('Promise 1');
}).then(function() {
  console.log('Promise 2');
});

console.log('End');

输出顺序:

Start
End
Promise 1
Promise 2
Timeout