从语法、语义到运行时

JavaScript 作为一门强大的语言,其复杂性常常让学习者感到困惑。

你是否曾纠结于 this 的指向,疑惑于闭包的神奇,或被事件循环的机制所困扰?要真正掌握 JavaScript,我们需要建立一个系统化的认知框架。本文将从 语法(Syntax)语义(Semantics)运行时行为(Runtime) 三个核心维度,带你重新梳理 JavaScript 的知识体系,让你不仅「知其然」,更「知其所以然」。


一、语法(Syntax):代码的「宪法」

语法是代码的形式与结构,是一套硬性规则,定义了如何编写出合法的 JavaScript 程序。 它关心的是 「怎么写不会报 SyntaxError」

1. 词汇结构(Lexical Structure)

  • 字符集:基于 Unicode,严格区分大小写。

  • 注释:单行 // 与多行 /* ... */

  • 标识符:变量、函数名的命名规则(以字母、_$ 开头,后续可跟数字)。

  • 关键字与保留字:如 ifforfunctionletclass 等,具有特殊用途,不能用作标识符。

  • 字面量:直接表示值的符号,如:

    • 123(数字)
    • "hello"(字符串)
    • true(布尔值)
    • {name: "Jack"}(对象)
    • [1, 2, 3](数组)

2. 表达式(Expressions)

表达式是能产生一个值的代码单元。

  • 初级表达式:字面量、标识符、this

  • 运算符与复杂表达式

    • 算术:+ - * / % **
    • 比较:=====><
    • 逻辑:&&||!
    • 赋值:=+=-=
    • 条件(三元):condition ? expr1 : expr2
    • 模板字面量:`Hello ${name}`
    • 现代运算符:展开 ...、可选链 ?.、空值合并 ??

3. 语句(Statements)

语句执行动作或控制流程,通常以分号结尾或通过换行隐式结束。

  • 声明语句letconstfunctionclassimportexport

  • 流程控制

    • 条件:if...elseswitch
    • 循环:forwhiledo...while
    • 遍历:for...in(键)、for...of(值)
  • 跳转语句breakcontinuereturnthrow

  • 其他'use strict'(严格模式指令)、空语句 ;

4. 声明与作用域(Declarations & Scope)

  • 变量声明

    • var:函数作用域,变量提升。
    • let / const:块级作用域,存在暂时性死区(TDZ)。
  • 函数声明:整体会被提升。

💡 小结:语法是地基。 例如,let 1a; 是非法(标识符错误),if (condition) { 必须配对 }


二、语义(Semantics):代码的「灵魂」

语义定义了语法正确的代码代表什么含义,以及它们如何被评估。 它关心的是 「这段代码是什么意思?」

1. 类型系统(Types)

  • 原始类型UndefinedNullBooleanNumberStringBigIntSymbol

    • 不可变,按值传递。
  • 对象类型ObjectArrayFunctionDateRegExp

    • 可变,按引用传递。
  • 类型转换

    • 显式:Number("123")String(123)

    • 隐式:

      "5" - 2 // → 3
      "5" + 2 // → "52"
      if ("hello") {...} // truthy
    • Falsy 值false0""nullundefinedNaN

2. 执行上下文(Execution Context)

  • 创建阶段

    1. 绑定 this
    2. 创建词法环境
    3. 注册变量和函数声明(变量提升)
  • 执行阶段:逐行运行,进行赋值和调用。

3. 作用域与作用域链

  • 词法作用域:作用域由定义位置决定。
  • 作用域链:查找变量时,从当前作用域向外层逐级查找,直到全局。

4. this 的动态绑定

  • 默认绑定:严格模式 → undefined,非严格 → window/global
  • 隐式绑定:obj.foo()obj
  • 显式绑定:call / apply / bind
  • new 绑定:构造函数 → 新对象
  • 箭头函数:继承定义时外层作用域的 this

5. 闭包(Closure)

闭包使得函数能够「记住」诞生时的词法作用域。 它是 模块化、私有变量、回调 的基础。

💡 小结:语义是蓝图。 理解语义,才能预测代码行为,例如 a == ba === b 的区别。


三、运行时(Runtime):代码的「舞台」

运行时是 JavaScript 代码的执行环境和机制。 它关心的是 「代码如何被调度与执行」

1. 引擎核心

  • 调用栈(Call Stack):跟踪正在执行的函数(LIFO)。
  • 内存堆(Heap):存放对象等复杂数据。
  • 垃圾回收:标记-清除(Mark & Sweep)是常见算法。

2. 事件循环(Event Loop)

JavaScript 是单线程,但能处理异步任务,依赖事件循环。

  • Web APIs / Node APIs:处理异步操作(如 setTimeoutfetchfs.readFile)。
  • 回调队列(MacroTask Queue)setTimeout、DOM 事件回调。
  • 微任务队列(MicroTask Queue)Promise.thenMutationObserver

流程:

  1. 执行调用栈中的同步任务
  2. 清空所有微任务
  3. 执行一个宏任务
  4. 重复循环

3. 宿主环境(Host Environment)

  • 浏览器:DOM、BOM、Web APIs(如 XMLHttpRequestCanvas
  • Node.js:文件系统、网络、进程 API

💡 小结:运行时是调度系统。 例如:为什么 Promise.thensetTimeout(fn, 0) 更早执行。


四、三维联动实战分析

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}
// 输出:3, 3, 3
  1. 语法:for 循环、var 声明、setTimeout 调用都合法。
  2. 语义var i 是函数作用域 → 共享一个变量。闭包捕获的是变量引用。
  3. 运行时:for 循环先执行完毕 → i 已变为 3 → 回调入宏任务队列 → 执行时打印 3。

✅ 解决方案:使用 let 或 IIFE

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2

总结与启示

维度关心的问题核心概念
语法 (Syntax)代码怎么写合法?关键字、运算符、语句、结构
语义 (Semantics)代码是什么意思?类型、作用域、闭包、this、上下文
运行时 (Runtime)代码如何被调度执行?调用栈、事件循环、任务队列、微任务
  • 语法:武器库,保证代码正确。
  • 语义:内功心法,理解代码逻辑。
  • 运行时:战场地图,洞悉执行流与性能关键。

三维融会贯通,你就能建立起一个清晰而深刻的 JavaScript 世界观。