PhysChen.com
主页
物理
科普 研究
教学
IB 课程
编程
笔记 项目
随笔
所感 所思
摄影
Shenzhen Portrait Cats Others Wuhan Japan
关于
主页
物理
科普 研究
教学
IB 课程
编程
笔记 项目
随笔
所感 所思
摄影
Shenzhen Portrait Cats Others Wuhan Japan
关于
文章目录
    JavaScript 1. 基础语法 陈华的个人主页

    文章信息

    • 标题: JavaScript 1. 基础语法
    • 发布时间: 2026 年 7 月 10 日
    • 来源: https://physchen.com/zh-Hans/programming/notes/javascript-basic-syntax/
    • 摘要: JavaScript 的基础语法由 ECMAScript 规范、底层数据存储机制以及控制流运行逻辑共同决定。

    目录

      JavaScript 1. 基础语法

      2026 年 7 月 10 日
      • JavaScript 基础
      • JavaScript

      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 词法命名限制

      变量名与常量名(标识符)必须遵循以下硬性语法规则:

      1. 字符集限制:仅允许包含 Unicode 字符(含字母、汉字等)、数字(0-9)、美元符号 $ 和下划线 _。
      2. 首字符限制:数字不允许作为首字符。
      3. 大小写敏感:严格区分大小写(例如 variable 与 Variable 代表两个独立的标识符绑定)。
      4. 规避保留字:不能使用 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 原始数据类型

      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。
      2. BigInt:用于表示任意精度的整数,突破了 Number 类型 \pu{2^53 - 1} 的安全整数限制。
        • 语法:在整数字面量末尾追加字符 n(如 9007199254740991n)。
        • 约束:无法与 Number 类型进行隐式混合算术运算,必须通过 BigInt() 或 Number() 进行显式强制类型转换后再计算。
      3. String:不可变的 Unicode 字符序列。
        • 单引号 '...' 与双引号 "..." 在底层解析上完全同质。
        • 反引号 `...`(模板字符串)支持多行文本,并通过 ${expression} 语法支持运行时表达式求值与字符串插值。
      4. Boolean:仅包含逻辑真值 true 和逻辑假值 false。
      5. null:仅包含单一字面量的原始类型,在逻辑层面上表示“故意赋予的空值”或“不存在的对象引用”。
      6. undefined:仅包含单一字面量的原始类型,表示变量已经声明但尚未分配初始值。
      7. Symbol:用于创建全局唯一的不可变标识符,通常用作对象属性的键,以防属性名发生命名冲突。

      1.3.2 对象类型

      1. Object:键值对的结构化集合。
        • Array (数组):本质上是带有特殊 length 属性的特化对象,在规范的类型系统中不属于独立类型。

      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) 或使用一元 + 运算符。
      • 隐式转换:在发生减、乘、除、取余、算术比较等非加法算术运算时,引擎自动触发。
      • 转换规则说明:
      原始输入值转换后输出值说明
      undefinedNaN未定义状态无法进行数学量化
      null0空引用转换为数学 0
      true/false1/0逻辑二进制映射
      string0 或 数值 或 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 遵循规范定义的特殊规则:

      1. 在相等检查(==)中:规范规定 null == undefined 为 true。同时,它们在非严格相等下不会与任何其他值发生转换,判定结果均为不相等。
      2. 在关系比较(>, <, >=, <=)中:上述特殊规则失效,遵循常规数字转换规则。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 分支,来让多组不同的严格匹配值共享同一段底层的执行逻辑。
      上一篇 下一篇 JavaScript 2. 函数 2026 年 7 月 11 日
      Xiaohongshu Streamline Icon: https://streamlinehq.com Xiaohongshu
      © 2026 CHEN Hua All rights reserved
      闽ICP备2026003335号 · 粤公网安备44030002014022号
      © Hua Chen / PhysChen.com