目录
JavaScript 1. 基础语法
JavaScript 的基础语法由 ECMAScript 规范、数据存储机制以及控制流运行逻辑共同决定。
1.1 脚本执行与代码结构
1.1.1 <script> 标签解析机制
在 HTML 文档中,<script> 标签用于嵌入或引入 JavaScript 代码。当该标签声明了 src 属性引入外部脚本时,标签内部嵌套的所有行内 JavaScript 代码都将被引擎忽略,不进行编译与执行。
1.1.2 自动分号插入机制(ASI)的失效边界
JavaScript 引擎利用自动分号插入机制(Automatic Semicolon Insertion, ASI)识别换行符并在必要时隐式补全分号。然而, ASI 在特定的代码结构下不会自动插入分号。
若新一行的首个非空字符为以下字符之一, ASI 将不会插入分号:
- 左括号
( - 左方括号
[ - 左花括号
{ - 反引号 `(模板字符串字面量)
- 正斜杠
/(构成正则表达式字面量时) - 算术运算符
+、-、*、%
这导致前后两行被解析为连续的单个表达式,从而引发运行时 TypeError 或逻辑错误。因此,生产环境编写时建议显式书写分号。
{
console.log("--- ASI 失效场景测试 ---");
try {
let x = 1
[2, 3].forEach(num => console.log(`数字: ${num}`))
} catch (e) {
// 触发 TypeError: Cannot read properties of undefined
console.log(`ASI失效引发异常: ${e.name} - ${e.message}`);
}
}
1.1.3 严格模式(“use strict”)的运行上下文
"use strict" 是一条编译指示(Directive),用于启动 ECMAScript 5 引入的严格模式。
- 声明约束:该指令必须放置在脚本或函数的首行非空语句位置(属于指令序言(Directive Prologue)范畴,前方仅允许存在其他字面量串),否则无法激活严格模式。
- 行为变动:严格模式会消除部分语言早期的静默错误,将其改为直接抛出异常,例如:向未声明的变量赋值、修改不可写属性、删除不可配置属性等都会直接触发错误。
- 上下文约束:严格模式一旦在当前运行上下文中启用,便无法在运行时动态取消。
- 自动激活: ECMAScript 6 引入的类(Classes)和模块(Modules)在底层设计上默认并强制启用严格模式,无需显式声明
"use strict"。
1.1.4 注释规范
- 单行注释:以
//开头。 - 多行注释:以
/*开始,以*/结束。 - 语法边界:多行注释在词法解析上不支持嵌套。例如
/* ... /* ... */ ... */会导致后方的*/提前闭合注释,使后续内容变为非法语法并抛出编译时语法错误。
1.2 变量与常量
1.2.1 声明作用域与提升机制
现代 JavaScript 包含三种声明标识符的关键字,其作用域和提升特性存在差异:
| 关键字 | 作用域级别 | 变量提升 | 重复声明 | 暂时性死区 |
|---|---|---|---|---|
var | 函数级 / 全局 | 提升并初始化为 undefined | 允许 | 不存在 |
let | 块级({} 内部) | 提升但未初始化 | 不支持 | 存在 |
const | 块级({} 内部) | 提升但未初始化 | 不支持 | 存在 |
- 暂时性死区(Temporal Dead Zone, TDZ):使用
let或const声明的标识符虽然在编译时被提升到块级作用域顶部,但在执行流实际运行到该声明语句之前,该标识符处于不可访问状态。在此期间读取或写入该标识符会直接抛出ReferenceError。 const的行为特性:const锁定的是标识符与值或内存地址之间的绑定关系,而非值本身。因此,声明的原始类型不可变;如果声明的是引用类型(如对象或数组),其内部的属性或元素依然可以被修改。
1.2.2 词法命名限制
变量名与常量名(标识符)必须遵循以下硬性语法规则:
- 字符集限制:仅允许包含 Unicode 字符(含字母、汉字等)、数字(0-9)、美元符号
$和下划线_。 - 首字符限制:数字不允许作为首字符。
- 大小写敏感:严格区分大小写(例如
variable与Variable代表两个独立的标识符绑定)。 - 规避保留字:不能使用 ECMAScript 规范定义的保留字和关键字(如
if、class、extends、await)。
1.2.3 常量分类约定
在工程实践中,常量根据其生命周期赋值阶段分为两类:
- 编译时常量(硬编码常量):在代码编写阶段即已知且不可变的值。通常使用全大写字母加下划线命名(例如:
const CONFIG_TIMEOUT = 5000;)。 - 运行时常量(Runtime Constants):在程序运行期间通过计算、 I/O 操作或函数返回才能确定的不可变绑定。采用常规的小驼峰命名(例如:
const currentUserId = fetchUserId();)。
1.3 数据类型
JavaScript 包含 8 种内置数据类型,分为 7 种原始类型(Primitive Types)和 1 种对象类型(Object Type)。
1.3.1 原始数据类型
- Number:基于 IEEE 754 标准的 64 位双精度浮点数(能够精确表示 \pu{-2^53 + 1} 到 \pu{2^53 - 1} 之间的整数)。包含三个特殊数值:
Infinity:正无穷大(如1 / 0)。-Infinity:负无穷大(如-1 / 0)。NaN(Not a Number):表示非法的数学运算结果(如0 / 0或Math.sqrt(-1))。- 等值特性:
NaN是 JavaScript 中唯一与自身不相等的值(NaN === NaN为false)。 - 运算传递性:任何包含
NaN的算术运算,其最终返回结果皆为NaN。
- 等值特性:
- BigInt:用于表示任意精度的整数,突破了 Number 类型 \pu{2^53 - 1} 的安全整数限制。
- 语法:在整数字面量末尾追加字符
n(如9007199254740991n)。 - 约束:无法与 Number 类型进行隐式混合算术运算,必须通过
BigInt()或Number()进行显式强制类型转换后再计算。
- 语法:在整数字面量末尾追加字符
- String:不可变的 Unicode 字符序列。
- 单引号
'...'与双引号"..."在底层解析上完全同质。 - 反引号
`...`(模板字符串)支持多行文本,并通过${expression}语法支持运行时表达式求值与字符串插值。
- 单引号
- Boolean:仅包含逻辑真值
true和逻辑假值false。 - null:仅包含单一字面量的原始类型,在逻辑层面上表示“故意赋予的空值”或“不存在的对象引用”。
- undefined:仅包含单一字面量的原始类型,表示变量已经声明但尚未分配初始值。
- Symbol:用于创建全局唯一的不可变标识符,通常用作对象属性的键,以防属性名发生命名冲突。
1.3.2 对象类型
- Object:键值对的结构化集合。
- Array (数组):本质上是带有特殊
length属性的特化对象,在规范的类型系统中不属于独立类型。
- Array (数组):本质上是带有特殊
1.3.3 typeof 运算符的类型映射与边界缺陷
typeof 运算符(或一元函数式写法 typeof(x))用于返回操作数的类型字符串。其内置映射规则如下表所示:
| 操作数类型 | 返回值字符串 | 规范备注说明 |
|---|---|---|
| Undefined | "undefined" | 符合预期 |
| Null | "object" | 语言级设计缺陷:底层二进制前三位标志位全为 0 ,被误判为对象引用。为确保向前兼容性,该表现已被永久保留。 |
| Boolean | "boolean" | 符合预期 |
| Number | "number" | 符合预期 |
| BigInt | "bigint" | 符合预期 |
| String | "string" | 符合预期 |
| Symbol | "symbol" | 符合预期 |
| Function | "function" | 特例划分:在底层规范中,函数属于实现了 [[Call]] 内部方法的对象。为了区分可调用实体,typeof 对其单独映射。 |
| 其他任何对象(如 Array, Date) | "object" | 符合预期 |
1.4 宿主环境交互机制(以浏览器为例)
浏览器宿主环境提供了三个阻断式(Blocking)模态对话框函数。当这些函数被调用时,浏览器会强制暂停 JavaScript 引擎的执行主线程,同时挂起所有 DOM 交互与页面渲染,直至用户完成交互。
alert(message):中断流程执行,向用户展示单向文本信息,挂起线程直至用户点击“确定”按钮。prompt(title, [default]):展示带有文本输入框的对话框。- 用户点击“确定”:返回输入框内的当前字符串(若输入为空则返回空字符串
"")。 - 用户点击“取消”或按
Esc键:返回null。
- 用户点击“确定”:返回输入框内的当前字符串(若输入为空则返回空字符串
confirm(question):展示带有“确定”和“取消”双按钮的二选一对话框。- 用户点击“确定”:返回布尔值
true。 - 用户点击“取消”:返回布尔值
false。
- 用户点击“确定”:返回布尔值
1.5 类型转换规范
1.5.1 字符串转换
- 显式转换:调用
String(value)。 - 隐式转换:当执行二元加法且至少有一方为字符串时,引擎会自动调用内部
ToString操作将另一方转换为字符串,随后执行字符串拼接。
1.5.2 数字转换
- 显式转换:调用
Number(value)或使用一元+运算符。 - 隐式转换:在发生减、乘、除、取余、算术比较等非加法算术运算时,引擎自动触发。
- 转换规则说明:
| 原始输入值 | 转换后输出值 | 说明 |
|---|---|---|
undefined | NaN | 未定义状态无法进行数学量化 |
null | 0 | 空引用转换为数学 0 |
true/false | 1/0 | 逻辑二进制映射 |
string | 0 或 数值 或 NaN | 先裁切首尾空格。若裁切后为空字符串返回 0;若包含非合法数字字符,直接返回 NaN。 |
1.5.3 布尔转换
在逻辑判定条件、循环控制流或调用 Boolean(value) 时触发。
JavaScript 将所有值严格划分为两类:
- 假值(Falsy Values):只有
0、-0、0n(BigInt 零)、""(空字符串)、null、undefined、NaN、以及document.all(宿主特例)这 8 个值会转换为false。 - 真值(Truthy Values):除上述假值以外的所有值均转换为
true。"0"(非空字符串)、" "(包含空格的字符串)、{}(空对象)、[](空数组)在布尔转换中全部为true。
1.6 运算符
1.6.1 算术运算符与加法的隐式类型转换
包含 +(加)、-(减)、*(乘)、/(除)、%(取余)以及 **(幂运算)。
二元 + 运算符具备多模态特性:如果运算数中存在任一字符串,则该运算转换为字符串拼接。
{
console.log("--- 算术加法拼接测试 ---");
// "12"(数字 2 转换为字符串 "2")
console.log(`'1' + 2 结果: ${'1' + 2}`);
// "41"(自左向右结合:先计算 2+2=4,再计算 4+'1')
console.log(`2 + 2 + '1' 结果: ${2 + 2 + '1'}`);
}
1.6.2 一元 + 运算符
加在单个操作数前的一元 + 不执行任何算术加法,其底层逻辑是触发隐式的数字转换,其效果与显式调用 Number(...) 等价。
{
console.log("--- 一元 + 运算符测试 ---");
console.log(`+"" 结果: ${+""}`); // 0
console.log(`+"123" 结果: ${+"123"}`); // 123
}
1.6.3 赋值运算符的副作用
- 赋值运算符
=在写入内存的同时,会返回所赋的值。因此let c = 3 - (a = b + 1)属于合法语法。 - 链式赋值(如
a = b = c = 5)的结合方向为自右向左:先将5赋予c,随后c = 5表达式返回值5再赋予b,以此类推。
1.6.4 自增/自减(++, --)
自增和自减运算符具备高优先级,且只能作用于可写的变量(左值)。直接作用于字面量(如 5++)会抛出语法解析阶段的异常。
- 前置形式(
++counter):变量首先在内存中完成递增(+1),随后表达式返回自增后的最新值。 - 后置形式(
counter++):表达式首先暂存变量的原值并将其作为结果返回,随后变量在内存中完成递增(+1)。
1.6.5 逗号运算符的求值链
逗号运算符允许在一行内串联多个表达式。它的运算逻辑是:自左向右依次评估所有表达式,但丢弃前面所有表达式的返回值,仅返回最后一个表达式的计算结果。逗号运算符拥有整个 JavaScript 运算符体系中最低的优先级。
{
console.log("--- 逗号运算符测试 ---");
// 1. 触发 逗号运算符(Comma Operator)逻辑
// 圆括号强行构建表达式求值上下文。二元逗号运算符执行自左向右的评估链:
// 首先评估左侧表达式 '1 + 2'(计算得结果 3 后被词法解析器丢弃);
// 随后评估右侧表达式 '3 + 4'(计算得结果 7),并将其作为整个表达式的最终返回值赋予变量 a。
let a = (1 + 2, 3 + 4);
console.log(`变量 a 的最终值: ${a}`); // 输出原始数字类型:7
// 2. 触发 数组字面量(Array Initializer)语法
// 此处方括号内部的逗号仅作为元素分隔符(Element Separator)运行,不具备运算符功能。
// 引擎在堆内存中分别独立评估索引 0 位(1 + 2 = 3)与索引 1 位(3 + 4 = 7)的表达式,
// 随后在堆内存中初始化该特化对象(数组实例),并将该内存实体指针赋予变量 b。
let b = [1 + 2, 3 + 4];
// 3. 触发模板字符串的隐式类型转换
// 字符串插值语法 ${b} 会强制触发引擎内部的抽象操作 ToString(b)。
// 数组引用的 ToString 默认调用 Array.prototype.toString(),其底层逻辑为通过逗号将各个成员展平连接。
console.log(`变量 b 的最终值: ${b}`); // 数组 [3, 7] 被隐式转换为文本后输出:"3,7"
}
1.7 比较运算符
1.7.1 非严格相等(==)的宽松相等算法
当使用 == 比较两个不同类型的操作数时, JavaScript 遵循规范定义的 宽松相等算法(IsLooselyEqual)的分支判定规则。其底层执行路径依据操作数的类型组合执行对应的条件分支:
- 原始值转换路径:对于常规原始类型(
String、Boolean),算法优先执行数字转换(ToNumber);若其中一方为对象类型而另一方为原始类型,优先触发 原始值转换(ToPrimitive)将对象还原为原始数据。若等式两端均为引用类型,则直接进行引用地址判定,不触发ToPrimitive转换。 - 特例执行路径:对于
null与undefined的互比,规范直接判定为true,不触发任何类型转换。 - 跨类型数值判定:对于
BigInt与Number的比对,引擎在排除特殊值(如NaN、Infinity)后,直接比对它们在数学上的绝对数值,绕过数字类型转换通道。
{
console.log("--- 宽松相等多路分支判定测试 ---");
// 分支 1:null == undefined 触发特例执行路径,不执行数字转换
console.log(`null == undefined 断言: ${null == undefined}`); // true
// 分支 2:Object == String 优先触发 ToPrimitive 转换为原始字符串
// 数组 [ "a" ] 还原为原始值后为字符串 "a",最终转化为 "a" == "a" 的严格比对
console.log(`[ "a" ] == "a" 断言: ${[ "a" ] == "a"}`); // true
// 分支 3:BigInt == Number 直接进行跨类型绝对数值比对,不发生类型转换
console.log(`1n == 1 断言: ${1n == 1}`); // true
}
1.7.2 严格相等(===)与恒等断言
严格相等 === 不进行任何隐式类型转换。它要求等式两边的类型与值必须完全等同。
- 边界条件:
===认为+0与-0恒等(返回true);认为NaN与NaN不恒等(返回false)。 - 引用类型判定:对于对象、数组,
===校验的是它们在栈内存中的引用指针(内存地址),即使两个对象的结构与内容完全一致,只要不是同一个内存实体,结果即为false。
1.7.3 null 与 undefined 的特殊表现
在涉及逻辑比较时,null 和 undefined 遵循规范定义的特殊规则:
- 在相等检查(
==)中:规范规定null == undefined为true。同时,它们在非严格相等下不会与任何其他值发生转换,判定结果均为不相等。 - 在关系比较(
>,<,>=,<=)中:上述特殊规则失效,遵循常规数字转换规则。null被隐式转换为0,undefined被隐式转换为NaN。由于任何包含NaN的关系比较结果皆为false,导致undefined的所有关系比较均输出false。
{
console.log("--- null 与 undefined 特例测试 ---");
console.log(`null > 0: ${null > 0}`); // false(转换为 0 > 0)
console.log(`null == 0: ${null == 0}`); // false(== 判定中拒绝转换为 0)
console.log(`null >= 0: ${null >= 0}`); // true (转换为 0 >= 0)
console.log(`undefined > 0: ${undefined > 0}`); // false(转换为 NaN > 0)
console.log(`undefined == 0: ${undefined == 0}`); // false(== 判定中不与 0 相等)
}
1.8 条件分支控制
1.8.1 if 条件求值
if (...) 语句对括号内的表达式进行求值,并通过内部的 ToBoolean 抽象操作将结果隐式转换为布尔值,随后根据真假值决定代码块的分支走向。
1.8.2 条件运算符 ?(三元运算符)
语法结构:let result = condition ? value1 : value2;
- 语义特性:条件运算符本质上是一个表达式而非语句,它的职责是根据条件返回一个值并进行赋值。
- 使用规范:条件运算符不应替代结构性的
if语句去执行无赋值需求的、带副作用的操作代码块,以免降低代码可读性。
1.9 逻辑运算符与空值合并运算符
1.9.1 逻辑运算符的短路求值(Short-circuit Evaluation)
JavaScript 的逻辑运算符并不局限于返回布尔值,它们返回的是操作数实体本身,并遵循 短路求值(Short-circuit Evaluation) 逻辑:
||(逻辑或):自左向右评估。一旦遇到第一个真值(Truthy),立即停止后续评估,并直接返回该操作数的值;若全部操作数皆为 Falsy ,则返回最后一个操作数的值。&&(逻辑与):自左向右评估。一旦遇到第一个假值(Falsy),立即停止后续评估,并直接返回该操作数的值;若全部操作数皆为 Truthy ,则返回最后一个操作数的值。- 优先级规则:
&&的运算优先级高于||。 !(逻辑非):将操作数隐式转换为布尔值后取反。双重非!!value的作用完全等同于显式调用Boolean(value)。
1.9.2 空值合并运算符 ?? 的精确拦截
空值合并运算符 ?? 用于专门防范 空值(Nullish)。
- 判定规则:
value1 ?? value2只有当value1的求值结果为null或undefined时,才会评估并返回value2;否则一律返回value1。 - 与
||的差异:||无法区分有效的零值与空值(会将0、""视作无效假值并予以跳过)。而??视0、""、false为有效的定义数据,直接返回,不会触发右侧的后备值。 - 语法混用约束(Syntactic Restrictions):由于 ECMAScript 规范有意未定义空值合并运算符与逻辑运算符之间的相对 运算符优先级(Operator Precedence),在词法解析层面严禁将
??与&&或||在不加圆括号的情况下直接混写,否则会在编译时抛出语法错误(SyntaxError)。
1.10 循环控制流
1.10.1 三种基础循环结构
while (condition) { ... }:前置条件判定循环。在每次迭代启动前检查条件。do { ... } while (condition):后置条件判定循环。先执行一次循环体代码,随后检查条件。确保循环体至少被执行一次。for (begin; condition; step) { ... }:计数器驱动循环。- 块级作用域隔离:在
for (let i = 0; ...)中使用let声明的迭代变量i,其作用域被严格限制在循环体内部。每次循环迭代,引擎底层都会在内存中创建一个全新的、独立的变量i,并把上一次迭代结束时的值复制过来。 - 语句省略边界:
for语句括号内的三个段落都可以被独立省略。(;;)底层等同于构建了一个无条件循环。
- 块级作用域隔离:在
1.10.2 循环流干预控制
break:立即截断并终结当前距离最近的内层循环,将控制权移交给循环体下方的首行语句。continue:立即终止当前这一轮次迭代的剩余语句,直接跳入下一次迭代的条件检查(对于for循环则先执行step步进再检查条件)。- 语法限制:因为
break和continue属于语句而非表达式,语法上不支持将其置于三元运算符的分支中,否则会抛出SyntaxError。
1.10.3 循环标签(Labels)与多层级解耦
标签是一个合法的标识符后加冒号,挂载在循环语句之前(如 labelName: for...)。
- 应用场景:当发生多层级嵌套循环时,内层循环中的
break labelName;或continue labelName;能够直接跨越当前的词法上下文,去精准中断或跳过挂载了该标签的最外层循环。
1.11 switch 语句的分支匹配规范
switch 语句提供了一种针对多路值匹配的条件控制流替代方案。
{
console.log("--- switch 严格匹配与穿透效应测试 ---");
let x = "2";
switch(x) {
case 2:
console.log("匹配成功: 数字 2");
break;
case "2":
console.log("匹配成功: 字符串 '2'");
// 故意缺省 break 以演示穿透效应
default:
console.log("控制流穿透至下方分支");
}
}
- 匹配规则:
switch的底层判定使用严格相等(===)。这意味着 case 分支的值不仅数值要相等,类型也必须完全等同,引擎在匹配时不会触发任何隐式类型转换(例如switch(2)无法匹配case "2")。 - 穿透效应(Fall-through):当某个
case分支匹配成功后,如果该分支的代码块末尾未显式书写break语句,引擎将进入穿透状态:直接忽略后续所有 case 的条件判定,强行按顺序执行下方所有的代码块,直至遇到break语句或将整个switch结构执行完毕。 - case 分组合并:利用穿透效应,可以通过连续书写多个不带
break的case分支,来让多组不同的严格匹配值共享同一段底层的执行逻辑。