从语法、语义到运行时
JavaScript 作为一门强大的语言,其复杂性常常让学习者感到困惑。
你是否曾纠结于 this 的指向,疑惑于闭包的神奇,或被事件循环的机制所困扰?要真正掌握 JavaScript,我们需要建立一个系统化的认知框架。本文将从 语法(Syntax)、语义(Semantics) 和 运行时行为(Runtime) 三个核心维度,带你重新梳理 JavaScript 的知识体系,让你不仅「知其然」,更「知其所以然」。
一、语法(Syntax):代码的「宪法」
语法是代码的形式与结构,是一套硬性规则,定义了如何编写出合法的 JavaScript 程序。 它关心的是 「怎么写不会报 SyntaxError」。
1. 词汇结构(Lexical Structure)
-
字符集:基于 Unicode,严格区分大小写。
-
注释:单行
//与多行/* ... */。 -
标识符:变量、函数名的命名规则(以字母、
_、$开头,后续可跟数字)。 -
关键字与保留字:如
if、for、function、let、class等,具有特殊用途,不能用作标识符。 -
字面量:直接表示值的符号,如:
123(数字)"hello"(字符串)true(布尔值){name: "Jack"}(对象)[1, 2, 3](数组)
2. 表达式(Expressions)
表达式是能产生一个值的代码单元。
-
初级表达式:字面量、标识符、
this。 -
运算符与复杂表达式:
- 算术:
+ - * / % ** - 比较:
==、===、>、< - 逻辑:
&&、||、! - 赋值:
=、+=、-= - 条件(三元):
condition ? expr1 : expr2 - 模板字面量:
`Hello ${name}` - 现代运算符:展开
...、可选链?.、空值合并??
- 算术:
3. 语句(Statements)
语句执行动作或控制流程,通常以分号结尾或通过换行隐式结束。
-
声明语句:
let、const、function、class、import、export -
流程控制:
- 条件:
if...else、switch - 循环:
for、while、do...while - 遍历:
for...in(键)、for...of(值)
- 条件:
-
跳转语句:
break、continue、return、throw -
其他:
'use strict'(严格模式指令)、空语句;
4. 声明与作用域(Declarations & Scope)
-
变量声明:
var:函数作用域,变量提升。let/const:块级作用域,存在暂时性死区(TDZ)。
-
函数声明:整体会被提升。
💡 小结:语法是地基。
例如,let 1a; 是非法(标识符错误),if (condition) { 必须配对 }。
二、语义(Semantics):代码的「灵魂」
语义定义了语法正确的代码代表什么含义,以及它们如何被评估。 它关心的是 「这段代码是什么意思?」
1. 类型系统(Types)
-
原始类型:
Undefined、Null、Boolean、Number、String、BigInt、Symbol- 不可变,按值传递。
-
对象类型:
Object、Array、Function、Date、RegExp等- 可变,按引用传递。
-
类型转换:
-
显式:
Number("123")、String(123) -
隐式:
"5" - 2 // → 3 "5" + 2 // → "52" if ("hello") {...} // truthy -
Falsy 值:
false、0、""、null、undefined、NaN
-
2. 执行上下文(Execution Context)
-
创建阶段:
- 绑定
this - 创建词法环境
- 注册变量和函数声明(变量提升)
- 绑定
-
执行阶段:逐行运行,进行赋值和调用。
3. 作用域与作用域链
- 词法作用域:作用域由定义位置决定。
- 作用域链:查找变量时,从当前作用域向外层逐级查找,直到全局。
4. this 的动态绑定
- 默认绑定:严格模式 →
undefined,非严格 →window/global - 隐式绑定:
obj.foo()→obj - 显式绑定:
call/apply/bind - new 绑定:构造函数 → 新对象
- 箭头函数:继承定义时外层作用域的
this
5. 闭包(Closure)
闭包使得函数能够「记住」诞生时的词法作用域。 它是 模块化、私有变量、回调 的基础。
💡 小结:语义是蓝图。
理解语义,才能预测代码行为,例如 a == b 与 a === b 的区别。
三、运行时(Runtime):代码的「舞台」
运行时是 JavaScript 代码的执行环境和机制。 它关心的是 「代码如何被调度与执行」。
1. 引擎核心
- 调用栈(Call Stack):跟踪正在执行的函数(LIFO)。
- 内存堆(Heap):存放对象等复杂数据。
- 垃圾回收:标记-清除(Mark & Sweep)是常见算法。
2. 事件循环(Event Loop)
JavaScript 是单线程,但能处理异步任务,依赖事件循环。
- Web APIs / Node APIs:处理异步操作(如
setTimeout、fetch、fs.readFile)。 - 回调队列(MacroTask Queue):
setTimeout、DOM 事件回调。 - 微任务队列(MicroTask Queue):
Promise.then、MutationObserver。
流程:
- 执行调用栈中的同步任务
- 清空所有微任务
- 执行一个宏任务
- 重复循环
3. 宿主环境(Host Environment)
- 浏览器:DOM、BOM、Web APIs(如
XMLHttpRequest、Canvas) - Node.js:文件系统、网络、进程 API
💡 小结:运行时是调度系统。
例如:为什么 Promise.then 比 setTimeout(fn, 0) 更早执行。
四、三维联动实战分析
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// 输出:3, 3, 3- 语法:for 循环、
var声明、setTimeout调用都合法。 - 语义:
var i是函数作用域 → 共享一个变量。闭包捕获的是变量引用。 - 运行时: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 世界观。