JavaScript 运行时

执行上下文

let x = 10;

function tinesTen(a) {
  return a * 10;
}

let y = tinesTen(x);

console.log(y); // 100
  • 首先,声明一个变量 x,并赋值为 10。

  • 然后,声明一个函数 tinesTen,并传入参数 a。

  • 最后,调用 tinesTen 函数,并传入 x 作为参数,返回值为 100。

当 JavaScript 引擎执行代码时,它会创建执行上下文,每个执行上下文都有两个阶段:创建阶段和执行阶段。

创建阶段

当 JavaScript 引擎第一次执行脚本时,它会创建全局执行上下文。在此阶段,JavaScript 引擎执行一下任务:

  • 创建全局对象,即 Web 浏览器中的 window 对象,Node.js 中的 global 对象。

  • 创建 this 对象并将其绑定到全局对象。

  • 设置一个内存堆,用于存储变量和函数引用。

  • 将函数声明存储在内存堆中,并将全局执行上下文中的变量存储为 undefined 。

当 JavaScript 引擎执行上面的代码示例时,它在创建阶段执行一下操作:

  • 首先,将变量 x 和 y 以及函数 tinesTen 存储在全局执行上下文中。

  • 其次,将变量 x 和 y 初始化为 undefined。

执行阶段

在执行阶段,JavaScript 引擎逐行执行代码,为变量分配值,并执行函数调用。

对于每个函数调用,JavaScript 引擎都会创建一个新的函数执行上下文。

函数执行上下文类似于全局执行上下文。但是 JavaScript 引擎不会创建全局对象,而是创建 arguments 对象,该对象是函数所有参数的引用。

在我们的示例中,函数执行上下文创建了 arguments 对象,该对象引用传递给函数的所有参数,将 this 值设置为全局对象,并将 a 参数初始化为 undefined

在函数执行上下文的执行阶段,JavaScript 引擎将 10 分配给参数 a,并将结果 (100) 返回给全局执行上下文。

为了跟踪所有执行上下文,包括全局执行上下文和函数执行上下文,JavaScript 引擎使用 调用堆栈

调用栈

调用栈是 JavaScript 引擎用来跟踪其在调用多个函数的代码中的位置的一种方式。它包含有关当前正在运行的函数以及从该函数中调用的函数的信息。

调用栈的工作原理基于后进先出(LIFO)原则。这意味着最后添加到栈中的函数是第一个被执行的函数,而第一个添加到栈中的函数是最后一个被执行的函数。

执行脚本时,JavaScript 引擎都会为该函数创建一个函数执行上下文,将其推入调用栈顶部,并开始执行该函数。

如果一个函数调用另外一个函数,JavaScript 引擎将为被调用的函数创建一个新的函数执行上下文,并将其推入调用栈顶部。

当前函数完成后,JavaScript 引擎会将其从调用栈中探出并恢复之前的状态。当调用栈为空时,JavaScript 脚本将停止执行。

调用栈示例

  • 当 JavaScript 引擎执行此脚本时,它会将全局执行上下文(用 main()global() 函数表示)放入调用栈。

  • 全局执行上下文进入创建阶段,然后进入执行阶段。

  • JavaScript 引擎执行到 console.log("One!"); 时,会将 console.log("One!"); 推入调用栈。执行完毕后从调用栈中弹出。

  • JavaScript 引擎执行到 console.log("Two!"); 时,会将 console.log("Two!"); 推入调用栈。执行完毕后从调用栈中弹出。

  • JavaScript 引擎执行 logThreeAndFour 函数,并为该函数创建一个函数执行上下文,并将其推入调用栈。

  • JavaScript 引擎开始执行 logThreeAndFour 函数,因为 logThreeAndFour 函数位于调用栈顶部。

  • logThreeAndFour 函数调用 logThree 函数,并为该函数创建一个函数执行上下文,并将其推入调用栈。

  • JavaScript 引擎执行 logThree 函数,并将其从调用栈中弹出。

  • 此时,logThreeAndFour 函数位于调用栈顶部。JavaScript 引擎执行并将其从调用栈中弹出。

  • 现在,调用栈为空,因此脚本停止执行。

栈溢出

调用栈的大小是固定的,取决于宿主环境(浏览器或 Node.js)。如果调用栈中执行上下文的数量超过栈的容量,则会发生栈溢出。

例如,执行一个递归函数时,如果它没有退出条件,JavaScript 引擎将发出栈溢出错误。

事件循环

JavaScript 是一种单线程编程语言,这意味着 JavaScript 引擎只有一个调用栈。因此它一次只能执行一个操作。

执行脚本时,JavaScript 引擎会从上到下、逐行执行代码。在执行阶段将函数压入和弹出调用栈。换句话说,它是同步的。

如果一个函数需要很长时间才能执行完,在函数执行期间就无法与浏览器进行交互,在这一段时间内,页面会卡住。

异步意味着 JavaScript 引擎可以在等待另一个任务完成时执行其他任务。例如,向远程服务器发送请求,并显示一个加载动画,直到请求完成,将结果显示给用户。

为此,JavaScript 引擎使用事件循环和回调函数来处理异步操作。

回调函数

为了防止阻塞函数阻塞其它代码的执行,通常将它放入回调韩素以供稍后执行,例如:

在这个例子中,你将立即看到 "Start Script" 和 "Done!"。1秒后,你将看到消息 "Download a file..."

JavaScript 引擎统同一时刻只能执行一项。准确的说,JavaScript 运行时一次只能执行一项操作。而浏览器还有其它组件,不仅仅是 JavaScript 引擎。

当调用 setTimeout 函数、发出 fetch 请求或点击按钮时,浏览器可以并发地执行这些任务。

在上述示例中,当调用 setTimeout 函数时,JavaScript 引擎会将其放在调用堆栈中,而Web Api会创建一个在 1 秒后执行的定时器。然后 JavaScript 引擎将 task 函数放入一个名为回调队列或任务队列的队列中。

是事件循环是一个不断运行的过程,它监控回调队列和调用堆栈。如果调用堆栈不为空,事件循环将等待它为空,然后将回调队列中的下一个函数放置到调用堆栈中。如果回调队列为空,则不会发生任何事情。

最后更新于

这有帮助吗?