# JavaScript 运行时

## 执行上下文

```js
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 脚本将停止执行。

### 调用栈示例

```js
console.log("One!");

console.log("Two!");

function logThree() {
  console.log("Three!");
}

function logThreeAndFour() {
  logThree();
  console.log("Four!");
}

logThreeAndFour();

console.log("Five!");
```

* 当 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 引擎执行并将其从调用栈中弹出。
* 现在，调用栈为空，因此脚本停止执行。

{% embed url="<https://vimeo.com/1082454151?share=copy>" %}

### 栈溢出

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

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

```js
function recursiveFunction() {
  recursiveFunction();
}

recursiveFunction(); // stack overflow
```

## 事件循环

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

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

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

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

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

### 回调函数

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

```javascript
console.log("Start Script");
setTimeout(()=>{
    task("Download a file...")
}, 1000);
console.log("Done!");
```

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

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

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

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

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