Published on

JavaScript 的原理

Functions (函数)

函数是 JavaScript 中可执行代码的基本单元。它们本身只是存储在内存中的对象,但在被调用 (invoked) 时,就成为了整个执行流程的触发器。每当一个函数被调用,它就会请求 JavaScript 引擎为它创建一个专属的运行环境,也就是一个新的执行上下文


Execution Context (执行上下文)

执行上下文是 JavaScript 引擎执行代码时所需的完整环境。把它想象成一个独立的盒子,每个盒子都包含两样东西:

  1. 内存空间 (Memory / Variable Environment):这个部分用来存储当前上下文中声明的所有变量、函数和参数。在上下文的“创建阶段”,JavaScript 引擎会扫描代码,找到所有声明并在这个内存空间中为它们分配好位置(这就是所谓的“变量提升”)。

  2. 执行线程 (Thread of Execution):这个部分负责逐行读取、解析并执行代码。在上下文的“执行阶段”,执行线程会一行一行地运行你的逻辑。

因此,当代码执行时,实际上是执行线程在操作内存空间中的数据。

两种执行上下文

  • 全局执行上下文:为整个脚本创建,包含全局变量和函数
  • 函数执行上下文:为每个函数调用创建,包含该函数的局部变量和参数。

Call Stack (调用栈)

调用栈(或称执行栈)是一个具有后进先出 (LIFO) 特点的数据结构,专门用来管理和追踪所有执行上下文。为了更好地理解,我们来看一个具体的例子:

示例代码:

function functionB() {
  // 断点 3: 进入 functionB
  console.log('In B');
}

function functionA() {
  // 断点 2: 进入 functionA
  console.log('Calling B');
  functionB();
  console.log('Returned from B');
}

// 断点 1: 脚本开始
console.log('Script Start');
functionA();
console.log('Script End');

下面是上面代码执行时,调用栈的变化过程的可视化图解:

  1. 脚本开始执行,functionA() 即将被调用。此时,只有全局执行上下文在调用栈中。
  1. 调用 functionA()functionA 的执行上下文被创建并压入栈顶。引擎现在开始执行 functionA 内部的代码。
  1. functionA 内部调用 functionB()functionB 的执行上下文被创建并压入栈顶。引擎暂停执行 functionA,转而开始执行 functionB 的代码。
  1. functionB() 执行完毕并返回,functionB 的执行上下文从栈顶被弹出。控制权交还给 functionA,从它上次暂停的地方继续执行。
  1. functionA() 执行完毕并返回,functionA 的执行上下文也被弹出。控制权交还给全局执行上下文。

最后,当所有全局代码执行完毕,全局执行上下文也从栈中弹出,调用栈清空,程序结束。