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

    文章信息

    • 标题: JavaScript 1. 基础语法
    • 发布时间: 2026 年 7 月 10 日
    • 最近更新: 2026 年 7 月 19 日
    • 来源: https://physchen.com/zh-Hans/programming/notes/javascript-basic-syntax/
    • 摘要: 系统介绍 JavaScript 的代码结构、变量声明、内置数据类型、类型转换、运算符与控制流,并区分 ECMAScript 语言规则、宿主环境和引擎实现。

    目录

      JavaScript 1. 基础语法

      发布于 2026 年 7 月 10 日 更新于 2026 年 7 月 19 日
      • JavaScript 基础
      • JavaScript

      JavaScript 程序由表达式⁠、语句⁠、声明和代码块组成⁠。理解基础语法不仅意味着记住关键字和运算符⁠,还需要区分三个层次⁠:

      1. ECMAScript 语言规范规定语法⁠、类型⁠、运算和执行语义⁠;
      2. 宿主环境提供浏览器 DOM⁠、定时器⁠、文件系统或网络接口⁠;
      3. JavaScript 引擎负责解析⁠、编译和执行代码⁠,并可以采用不同的内部优化策略⁠。

      ECMAScript 规范通常使用环境记录⁠、引用记录⁠、内部槽和抽象操作描述程序行为⁠。这些概念用于定义可观察语义⁠,并不等同于某一种固定的栈⁠、堆或指针布局⁠。因此⁠,除非明确讨论某个引擎⁠,否则不应把规范抽象直接解释为物理内存结构⁠。

      1.1 ECMAScript⁠、JavaScript 引擎与宿主环境

      JavaScript 通常指以 ECMAScript 为语言核心⁠,并运行在浏览器⁠、Node.js 等宿主环境中的编程体系⁠。

      ECMAScript 本身定义了以下内容⁠:

      • 词法和语法规则⁠;
      • 内置数据类型⁠;
      • 表达式和语句的求值规则⁠;
      • 函数⁠、对象⁠、类和模块的语言语义⁠;
      • Array⁠、Map⁠、Promise 等标准内置对象⁠。

      浏览器则额外提供⁠:

      • window⁠、document 和 DOM⁠;
      • fetch()⁠;
      • setTimeout()⁠;
      • 用户交互事件⁠;
      • 页面渲染相关接口⁠。

      Node.js 也提供自己的文件系统⁠、进程⁠、网络和定时器接口⁠。相同的 ECMAScript 代码可以在不同宿主中执行⁠,但宿主 API 并不一定通用⁠。

      1.1.1 在 HTML 中加载脚本

      HTML 可以通过 <script> 标签嵌入或引入 JavaScript⁠:

      <script>
        console.log("inline script");
      </script>
      
      <script src="/scripts/main.js"></script>

      当 <script> 标签具有 src 属性时⁠,它表示加载外部脚本⁠。此时标签内部的 JavaScript 文本不会作为该脚本的内容执行⁠:

      <script src="/scripts/main.js">
        console.log("这段代码不会执行");
      </script>

      传统脚本与模块脚本具有不同的作用域和加载语义⁠:

      <script src="/scripts/legacy.js"></script>
      <script type="module" src="/scripts/main.js"></script>

      模块默认运行在严格模式下⁠,并具有独立的模块作用域⁠。模块系统将在第九章系统讨论⁠。

      1.1.2 表达式⁠、语句与声明

      表达式会求值得到一个值⁠:

      1 + 2;
      user.name;
      isReady ? "ready" : "waiting";

      语句用于控制程序执行⁠:

      if (isReady) {
        start();
      }

      声明用于创建变量⁠、函数⁠、类或模块绑定⁠:

      const language = "JavaScript";
      
      function greet() {
        return "Hello";
      }

      某些结构既包含声明⁠,也包含表达式⁠。理解二者的区别有助于判断一段代码能否出现在某个语法位置⁠。

      1.1.3 代码块与注释

      代码块由一对花括号组成⁠:

      {
        const value = 10;
        console.log(value);
      }

      let⁠、const 和类声明具有块级作用域⁠。普通对象字面量也使用花括号⁠,但它是表达式的一部分⁠,不是代码块⁠。

      JavaScript 支持两种注释⁠:

      // 单行注释
      
      /*
        多行注释
      */

      多行注释不能嵌套⁠。注释应说明设计意图⁠、边界条件或不明显的原因⁠,而不是逐字复述代码⁠。

      1.2 自动分号插入与严格模式

      1.2.1 自动分号插入

      JavaScript 中的许多语句在语法上以分号结束⁠。源代码省略分号时⁠,解析器可以在规范规定的条件下执行自动分号插入(⁠Automatic Semicolon Insertion⁠,ASI⁠)⁠。ASI 并不是简单地“⁠在每个换行处补上分号⁠”⁠。如果换行后的标记仍能与上一行构成合法表达式⁠,解析器通常不会在两行之间插入分号⁠。

      const value = 10
      
      (function () {
        console.log("IIFE");
      })();

      这段代码可能被理解为尝试调用数值 10⁠,而不是两条独立语句⁠。类似风险常见于下一行以下列标记开始时⁠:

      • (⁠;
      • [⁠;
      • 模板字面量的反引号⁠;
      • /⁠;
      • 一元 + 或 -⁠。

      例如⁠:

      const numbers = [1, 2, 3]
      
      [4, 5].forEach((value) => {
        console.log(value);
      });

      如果上一行没有分号⁠,后一行的方括号可能被解析为对上一表达式结果进行属性访问⁠。

      一种可靠风格是在所有可写分号的位置显式书写分号⁠:

      const numbers = [1, 2, 3];
      
      [4, 5].forEach((value) => {
        console.log(value);
      });

      另一种风格是整体省略分号⁠,但在以危险标记开头的语句前添加防御性分号⁠。无论选择哪种风格⁠,都应保持一致⁠,并交由格式化工具统一处理⁠。

      1.2.2 受限产生式中的换行

      某些语法位置明确禁止换行⁠。最常见的是 return⁠:

      function createUser() {
        return
        {
          name: "Alice",
        };
      }
      
      console.log(createUser()); // undefined

      解析器会将其视为⁠:

      function createUser() {
        return;
        {
          name: "Alice";
        }
      }

      应让返回表达式与 return 位于同一行⁠,或者在换行时使用圆括号⁠:

      function createUser() {
        return {
          name: "Alice",
        };
      }
      
      function createAnotherUser() {
        return (
          {
            name: "Bob",
          }
        );
      }

      类似的换行限制还会影响 throw⁠、带标签的 break 和 continue 等结构⁠。

      1.2.3 严格模式

      在传统脚本或普通函数体的指令序言中写入 "use strict"⁠,可以启用严格模式⁠:

      "use strict";
      
      const value = 10;

      指令序言必须位于脚本或函数体开头⁠,并由字符串字面量表达式构成⁠:

      function run() {
        "use strict";
      
        // 函数体在严格模式下执行。
      }

      严格模式会改变部分早期 JavaScript 行为⁠,例如⁠:

      • 给未声明的标识符赋值会抛出 ReferenceError⁠;
      • 独立调用普通函数时⁠,函数内部的 this 保持为 undefined⁠;
      • 某些静默失败会改为抛出异常⁠;
      • 删除不可删除的绑定等非法操作会更早暴露⁠。

      ECMAScript 模块和类体默认处于严格模式⁠,无需额外声明⁠。严格模式不能在已经执行的代码中动态关闭⁠。它改变的是相应源代码区域的解析与执行语义⁠,而不是一个可以随时切换的全局开关⁠。

      1.3 变量声明⁠、作用域与绑定

      1.3.1 let⁠、const 与 var

      现代 JavaScript 主要使用 let 和 const 声明变量⁠。三种声明方式的核心差异如下⁠:

      声明作用域声明前访问同一作用域重复声明是否允许重新赋值
      var函数或全局作用域返回 undefined允许允许
      let块级作用域抛出 ReferenceError不允许允许
      const块级作用域抛出 ReferenceError不允许不允许
      {
        let count = 1;
        const limit = 10;
      
        count += 1;
      
        console.log(count); // 2
        console.log(limit); // 10
      }

      推荐默认使用 const⁠。只有当变量绑定确实需要重新赋值时⁠,才使用 let⁠。现代代码通常不需要使用 var⁠。

      1.3.2 暂时性死区

      包含 let⁠、const 或类声明的作用域在开始执行时⁠,会先建立相应绑定⁠。但在执行到声明语句并完成初始化之前⁠,这些绑定不能被访问⁠。从作用域开始到初始化完成之间的区域称为暂时性死区(⁠Temporal Dead Zone⁠,TDZ⁠)⁠:

      {
        console.log(value); // ReferenceError
      
        const value = 10;
      }

      TDZ 并不意味着变量“⁠完全不存在⁠”⁠,而是相应词法绑定已经属于当前作用域⁠,但仍处于未初始化状态⁠。

      这一规则还会阻止代码错误地访问外层同名变量⁠:

      const value = "outer";
      
      {
        console.log(value); // ReferenceError
        const value = "inner";
      }

      块内的 value 从块开始处就遮蔽了外层绑定⁠,但在声明完成前不可访问⁠。

      1.3.3 var 提升

      var 声明的绑定在进入函数或脚本执行阶段前已经初始化为 undefined⁠:

      console.log(value); // undefined
      
      var value = 10;

      它在行为上近似于⁠:

      var value;
      
      console.log(value);
      
      value = 10;

      这不表示赋值语句也被移动⁠。所谓“⁠提升⁠”是对环境初始化结果的通俗描述⁠,而不是引擎必须实际重排源代码⁠。

      1.3.4 const 限制的是绑定

      const 阻止标识符被重新赋值⁠:

      const score = 90;
      
      // TypeError
      score = 100;

      如果绑定的值是对象⁠,仍然可以修改对象自身允许修改的属性⁠:

      const student = {
        name: "Alice",
        score: 90,
      };
      
      student.score = 100;
      
      console.log(student.score); // 100

      下面的重新赋值仍然非法⁠:

      const student = {
        name: "Alice",
      };
      
      // TypeError
      student = {
        name: "Bob",
      };

      不应把这一规则描述为“⁠锁定栈内存中的指针⁠”⁠。ECMAScript 规范只保证绑定不可重新赋值⁠,并不规定该值必须以某种指针形式存放在某个物理内存区域⁠。

      如需限制对象属性⁠,可以使用 Object.freeze()⁠,但它默认只进行浅层冻结⁠:

      const settings = Object.freeze({
        theme: "dark",
      });
      
      // 严格模式下抛出 TypeError;非严格模式下通常静默失败。
      settings.theme = "light";

      1.3.5 标识符规则

      JavaScript 标识符可以包含 Unicode 标识符字符⁠、数字⁠、美元符号 $ 和下划线 _⁠,但数字不能作为首字符⁠。

      const userName = "Alice";
      const _internalValue = 10;
      const $element = document.querySelector("#app");
      const 学科 = "Physics";

      JavaScript 区分大小写⁠:

      const value = 1;
      const Value = 2;
      
      console.log(value); // 1
      console.log(Value); // 2

      关键字和保留字不能在不允许的位置作为标识符使用⁠。

      工程命名通常遵循以下约定⁠:

      • 变量和函数使用小驼峰⁠:currentUser⁠、calculateTotal()⁠;
      • 类使用大驼峰⁠:StudentRecord⁠;
      • 已知且具有全局常量语义的值可以使用全大写下划线⁠:MAX_RETRY_COUNT⁠;
      • 普通 const 变量不需要全部使用大写⁠。

      1.4 JavaScript 数据类型

      JavaScript 具有 7 种原始类型和对象类型⁠。

      原始类型包括⁠:

      1. undefined⁠;
      2. null⁠;
      3. boolean⁠;
      4. number⁠;
      5. bigint⁠;
      6. string⁠;
      7. symbol⁠。

      除原始值之外的语言值都属于对象⁠。数组⁠、函数⁠、日期⁠、正则表达式⁠、Map 和 Set 都是对象的不同形式⁠。

      1.4.1 值语义与对象引用

      原始值是不可变值⁠。把原始值赋给另一个变量时⁠,两个绑定分别持有相同的值⁠:

      let first = 10;
      let second = first;
      
      second = 20;
      
      console.log(first);  // 10
      console.log(second); // 20

      对象赋值会复制对象引用所代表的值⁠,因此两个变量可以引用同一个对象⁠:

      const first = {
        score: 90,
      };
      
      const second = first;
      
      second.score = 100;
      
      console.log(first.score); // 100

      这里应表述为“⁠两个绑定引用同一个对象⁠”⁠,而不是假定规范要求复制某个物理内存地址⁠。对象的实际存储位置和优化方式属于引擎实现细节⁠。

      1.4.2 Number

      JavaScript 的 Number 使用 IEEE 754 双精度浮点数语义表示普通数值⁠。

      const integer = 42;
      const decimal = 3.14;
      const scientific = 1.5e6;

      Number 同时表示整数和浮点数⁠。可安全精确表示的整数范围为⁠:

      −(253−1)≤n≤253−1-(2^{53}-1)\leq n\leq 2^{53}-1−(253−1)≤n≤253−1

      对应常量为⁠:

      console.log(Number.MIN_SAFE_INTEGER);
      console.log(Number.MAX_SAFE_INTEGER);

      判断整数是否位于安全范围内⁠,可以使用⁠:

      console.log(
        Number.isSafeInteger(9007199254740991),
      ); // true
      
      console.log(
        Number.isSafeInteger(9007199254740992),
      ); // false

      Infinity 与负无穷

      某些数值运算会得到正无穷或负无穷⁠:

      console.log(1 / 0);  // Infinity
      console.log(-1 / 0); // -Infinity

      Infinity 仍然属于 number⁠:

      console.log(typeof Infinity); // number

      判断一个数值是否有限⁠,推荐使用 Number.isFinite()⁠:

      console.log(Number.isFinite(10));       // true
      console.log(Number.isFinite(Infinity)); // false
      console.log(Number.isFinite("10"));     // false

      全局 isFinite() 会先执行数字转换⁠,语义更宽松⁠:

      console.log(isFinite("10")); // true

      NaN

      NaN 表示数值运算未能产生有效数值结果⁠:

      console.log(0 / 0);          // NaN
      console.log(Number("text")); // NaN

      NaN 与包括自身在内的任何值进行 === 比较都返回 false⁠:

      console.log(NaN === NaN); // false

      检测 NaN 应使用 Number.isNaN()⁠:

      console.log(Number.isNaN(NaN));    // true
      console.log(Number.isNaN("text")); // false

      全局 isNaN() 会先进行数字转换⁠,容易产生误解⁠:

      console.log(isNaN("text")); // true

      大多数涉及 NaN 的普通算术运算仍会得到 NaN⁠,但不能把它理解为所有操作中的绝对传播定律⁠。例如字符串拼接可以把它转换为字符串⁠:

      console.log(NaN + 1);   // NaN
      console.log(`${NaN}`);  // "NaN"

      浮点误差

      二进制浮点数不能精确表示所有十进制小数⁠:

      console.log(0.1 + 0.2); // 0.30000000000000004

      判断浮点结果是否足够接近时⁠,可以根据问题尺度选择容差⁠:

      const result = 0.1 + 0.2;
      const expected = 0.3;
      
      console.log(
        Math.abs(result - expected) < Number.EPSILON,
      ); // true

      Number.EPSILON 适合描述 1 附近相邻可表示数的间隔⁠,并不是适用于所有数量级的通用误差阈值⁠。实际计算应根据数据尺度构造相对或绝对容差⁠。

      1.4.3 BigInt

      BigInt 用于表示任意精度整数⁠:

      const largeValue = 9007199254740993n;
      
      console.log(typeof largeValue); // bigint

      BigInt 不能与 Number 直接混合执行普通算术运算⁠:

      // TypeError
      // console.log(1n + 1);

      必须先显式转换到同一种类型⁠:

      console.log(1n + BigInt(1)); // 2n
      console.log(Number(1n) + 1); // 2

      把较大的 BigInt 转换为 Number 可能丢失精度⁠:

      const exact = 9007199254740993n;
      const approximate = Number(exact);
      
      console.log(approximate); // 9007199254740992

      BigInt 除法会舍弃小数部分⁠:

      console.log(7n / 2n); // 3n

      1.4.4 String

      字符串是不可变的 UTF-16 代码单元序列⁠:

      const single = 'JavaScript';
      const double = "JavaScript";
      const template = `JavaScript`;

      单引号和双引号没有语义差异⁠。模板字面量支持多行文本和表达式插值⁠:

      const name = "Alice";
      const score = 95;
      
      const message = `${name}'s score is ${score}.`;
      
      console.log(message);

      字符串不能原地修改⁠:

      const language = "JavaScript";
      
      language[0] = "j";
      
      console.log(language); // JavaScript

      某些 Unicode 字符由两个 UTF-16 代码单元组成⁠,因此字符串的 length 不一定等于用户感知的字符数量⁠:

      console.log("😀".length); // 2

      更完整的 Unicode 和国际化处理将在第七章讨论⁠。

      1.4.5 Boolean

      布尔类型只有两个值⁠:

      const enabled = true;
      const disabled = false;

      布尔值主要用于条件判断⁠,但 JavaScript 的条件表达式也允许其他类型⁠,并通过布尔转换决定分支⁠。

      1.4.6 null 与 undefined

      undefined 通常表示缺少值或尚未提供值⁠:

      let result;
      
      console.log(result); // undefined

      函数没有显式返回值时⁠,也会返回 undefined⁠。

      null 通常由程序显式使用⁠,表示有意设置的空值⁠:

      let selectedUser = null;

      二者语义接近但类型不同⁠:

      console.log(null === undefined); // false
      console.log(null == undefined);  // true

      在现代代码中⁠,应根据接口语义明确选择 null 或 undefined⁠,而不是随意混用⁠。

      1.4.7 Symbol

      每次调用 Symbol() 都会创建一个唯一值⁠:

      const first = Symbol("id");
      const second = Symbol("id");
      
      console.log(first === second); // false

      Symbol 可以用作对象属性键⁠,以避免普通字符串键名冲突⁠:

      const internalId = Symbol("internalId");
      
      const user = {
        name: "Alice",
        [internalId]: 1001,
      };
      
      console.log(user[internalId]); // 1001

      全局 Symbol 注册表中的 Symbol.for() 具有不同语义⁠:

      const first = Symbol.for("shared");
      const second = Symbol.for("shared");
      
      console.log(first === second); // true

      Symbol 的系统应用将在对象⁠、迭代器和元编程章节中继续讨论⁠。

      1.4.8 Object

      对象用于保存属性和行为⁠:

      const student = {
        name: "Alice",
        score: 95,
      
        introduce() {
          return `I am ${this.name}.`;
        },
      };

      数组和函数也是对象⁠:

      console.log(typeof []);          // object
      console.log(typeof function() {}); // function

      函数之所以返回 "function"⁠,是因为 typeof 对可调用对象具有专门分支⁠。函数仍然属于对象⁠,可以拥有属性并作为值传递⁠。对象和数组的属性⁠、复制⁠、遍历及内存拓扑将在第五章系统讨论⁠。

      1.5 typeof 与更精确的类型检测

      1.5.1 typeof 的返回结果

      typeof 是一元运算符⁠:

      console.log(typeof 42); // number

      写成 typeof(value) 也合法⁠,但圆括号并不表示它是普通函数调用⁠。

      值typeof 结果
      undefined"undefined"
      null"object"
      true"boolean"
      42"number"
      42n"bigint"
      "text""string"
      Symbol()"symbol"
      普通函数"function"
      数组及其他普通对象"object"

      1.5.2 typeof null

      console.log(typeof null); // object

      这是 JavaScript 的历史遗留行为⁠。它通常被追溯到早期实现使用类型标签表示值时⁠,空指针式的 null 表示与对象标签发生冲突⁠。现代 ECMAScript 规范直接保留了这一可观察结果⁠,以维持既有网页兼容性⁠。这一历史解释不应被当作现代引擎必须采用的物理内存布局⁠。可靠结论只有一个⁠:语言规定 typeof null 返回 "object"⁠。检测 null 应直接使用严格相等⁠:

      console.log(value === null);

      1.5.3 数组和对象检测

      检测数组应使用⁠:

      console.log(Array.isArray([])); // true

      检测某个值是否为非空对象⁠,可以写成⁠:

      function isObject(value) {
        return value !== null && typeof value === "object";
      }

      instanceof 检查原型链关系⁠,不等同于通用类型检测⁠,并且会受到跨 realm⁠、原型修改和自定义 Symbol.hasInstance 的影响⁠。其机制将在第六章讨论⁠。

      1.6 类型转换

      JavaScript 既支持显式类型转换⁠,也会在部分运算中进行隐式类型转换⁠。

      显式转换通常更容易阅读⁠:

      const input = "42";
      const number = Number(input);

      隐式转换则由运算符或语句语义触发⁠:

      console.log("1" + 2); // "12"
      console.log("6" / 2); // 3

      1.6.1 字符串转换

      显式字符串转换使用 String()⁠:

      console.log(String(123));       // "123"
      console.log(String(true));      // "true"
      console.log(String(null));      // "null"
      console.log(String(undefined)); // "undefined"

      模板字面量插值也会把值转换为字符串⁠:

      const value = 42;
      
      console.log(`value = ${value}`);

      直接把 Symbol 与普通字符串使用 + 拼接会抛出 TypeError⁠:

      const id = Symbol("id");
      
      // TypeError
      // console.log("id: " + id);
      
      console.log(`id: ${String(id)}`);

      1.6.2 数字转换

      显式数字转换使用 Number()⁠:

      console.log(Number("42"));    // 42
      console.log(Number("  "));    // 0
      console.log(Number("3.14"));  // 3.14
      console.log(Number("3px"));   // NaN
      console.log(Number(true));    // 1
      console.log(Number(false));   // 0
      console.log(Number(null));    // 0
      console.log(Number(undefined)); // NaN

      一元 + 也会触发普通数字转换⁠:

      console.log(+"42"); // 42
      console.log(+"");   // 0

      一元 + 不能用于 BigInt⁠:

      // TypeError
      // console.log(+1n);

      parseInt() 和 parseFloat() 用于从字符串开头解析数字⁠,其语义与 Number() 不同⁠:

      console.log(Number("12px"));         // NaN
      console.log(Number.parseInt("12px", 10)); // 12
      console.log(Number.parseFloat("3.14m"));  // 3.14

      使用 Number.parseInt() 时通常应显式提供进制⁠。

      1.6.3 布尔转换

      显式布尔转换使用 Boolean()⁠:

      console.log(Boolean(1));  // true
      console.log(Boolean(0));  // false

      条件语句和逻辑运算符会隐式执行布尔转换⁠。

      普通 ECMAScript 值中的假值包括⁠:

      • false⁠;
      • 0⁠;
      • -0⁠;
      • 0n⁠;
      • ""⁠;
      • null⁠;
      • undefined⁠;
      • NaN⁠。

      除此之外⁠,浏览器中还存在历史兼容对象 document.all⁠,它具有特殊的假值行为⁠。一般程序不应依赖这一例外⁠。所有其他值都是真值⁠,包括⁠:

      Boolean("0"); // true
      Boolean(" "); // true
      Boolean([]);  // true
      Boolean({});  // true

      空数组和空对象是真值⁠,因为布尔转换判断的是值的类型和规则⁠,而不是集合内部是否包含元素⁠。

      1.6.4 二元加法

      二元 + 同时承担数值加法和字符串拼接⁠。

      求值时⁠,操作数会先转换为原始值⁠。如果任一转换结果是字符串⁠,则执行字符串拼接⁠;否则尝试执行数值加法⁠。

      console.log(1 + 2);       // 3
      console.log("1" + 2);     // "12"
      console.log(1 + "2");     // "12"
      console.log(1 + 2 + "3"); // "33"
      console.log("1" + 2 + 3); // "123"

      对象也可能通过原始值转换参与运算⁠:

      console.log([] + 1); // "1"

      这类隐式转换规则容易产生难以理解的结果⁠。业务代码应避免依赖复杂的对象转换⁠,优先显式转换数据类型⁠。

      Number 与 BigInt 不能直接混合执行加法⁠:

      // TypeError
      // console.log(1 + 1n);

      1.6.5 其他算术运算

      减法⁠、乘法⁠、除法⁠、取余和幂运算不会执行字符串拼接⁠,而会尝试执行数值转换⁠:

      console.log("6" - 1); // 5
      console.log("6" * 2); // 12
      console.log("6" / 2); // 3

      如果转换失败⁠,结果通常为 NaN⁠:

      console.log("text" * 2); // NaN

      1.7 运算符

      1.7.1 算术运算符

      常见算术运算符包括⁠:

      运算符含义
      +加法或字符串拼接
      -减法
      *乘法
      /除法
      %余数
      **幂

      JavaScript 的 % 返回余数⁠,其符号通常与被除数一致⁠:

      console.log(7 % 3);   // 1
      console.log(-7 % 3);  // -1

      它不总是数学意义上的非负模运算⁠。需要非负模时⁠,可以封装⁠:

      function modulo(value, divisor) {
        return ((value % divisor) + divisor) % divisor;
      }
      
      console.log(modulo(-7, 3)); // 2

      1.7.2 赋值运算符

      赋值表达式本身会返回所赋的值⁠:

      let value;
      
      console.log(value = 10); // 10

      链式赋值从右向左结合⁠:

      let first;
      let second;
      
      first = second = 5;
      
      console.log(first);  // 5
      console.log(second); // 5

      复合赋值运算符包括⁠:

      value += 1;
      value -= 1;
      value *= 2;
      value /= 2;
      value **= 2;

      逻辑赋值运算符包括⁠:

      value ||= fallback;
      value &&= nextValue;
      value ??= fallback;

      它们具有短路求值语义⁠,不完全等价于不考虑副作用的简单展开式⁠。

      1.7.3 自增与自减

      前置形式先修改变量⁠,再返回新值⁠:

      let value = 1;
      
      console.log(++value); // 2
      console.log(value);   // 2

      后置形式先返回旧值⁠,再修改变量⁠:

      let value = 1;
      
      console.log(value++); // 1
      console.log(value);   // 2

      自增和自减只能作用于可赋值引用⁠,不能直接作用于字面量⁠:

      // SyntaxError
      // 5++;

      在复杂表达式中混合使用 ++⁠、-- 和其他副作用会降低可读性⁠。通常应将状态更新写成独立语句⁠。

      1.7.4 逗号运算符

      逗号运算符从左到右求值多个表达式⁠,并返回最后一个表达式的结果⁠:

      const result = (1 + 2, 3 + 4);
      
      console.log(result); // 7

      数组字面量⁠、函数实参列表和变量声明中的逗号通常是语法分隔符⁠,不是逗号运算符⁠:

      const values = [1 + 2, 3 + 4];
      
      console.log(values); // [3, 7]

      逗号运算符优先级很低⁠,实际业务代码中应谨慎使用⁠。

      1.7.5 运算符优先级

      const result = 2 + 3 * 4;
      
      console.log(result); // 14

      乘法优先于加法⁠。圆括号可以显式改变求值顺序⁠:

      const result = (2 + 3) * 4;
      
      console.log(result); // 20

      即使熟悉优先级⁠,在混合多个不同运算符时也应使用圆括号表达意图⁠。可读性比省略字符更重要⁠。

      1.8 相等⁠、关系与逻辑运算

      1.8.1 严格相等

      严格相等 === 不执行类型转换⁠:

      console.log(1 === 1);   // true
      console.log(1 === "1"); // false

      对于对象⁠,严格相等判断两个操作数是否引用同一个对象⁠:

      const first = {
        value: 1,
      };
      
      const second = {
        value: 1,
      };
      
      const third = first;
      
      console.log(first === second); // false
      console.log(first === third);  // true

      数值比较中⁠:

      console.log(NaN === NaN); // false
      console.log(+0 === -0);   // true

      1.8.2 Object.is

      Object.is() 使用 SameValue 语义⁠:

      console.log(Object.is(NaN, NaN)); // true
      console.log(Object.is(+0, -0));   // false

      其他常见值上⁠,它通常与 === 结果一致⁠。

      1.8.3 宽松相等

      宽松相等 == 会根据操作数类型执行规范规定的转换和特殊分支⁠:

      console.log(1 == "1");          // true
      console.log(false == 0);        // true
      console.log(null == undefined); // true

      它的行为不是简单地“⁠把两边都转换为数字⁠”⁠。例如⁠,null 与 undefined 之间有专门规则⁠,对象与原始值的比较还会触发原始值转换⁠。

      现代业务代码通常优先使用 === 和 !==⁠。一种常见且有明确意图的宽松相等用法是同时检测 null 与 undefined⁠:

      if (value == null) {
        // value 为 null 或 undefined
      }

      使用这种写法时应确保团队理解其语义⁠,否则也可以显式写出两个条件⁠。

      1.8.4 关系比较

      关系运算符包括⁠:

      <;
      >;
      <=;
      >=;

      字符串与字符串之间按照 UTF-16 代码单元序列进行比较⁠:

      console.log("2" < "10"); // false

      不同类型之间会发生相应转换⁠:

      console.log("2" < 10); // true

      null 与 undefined 在关系比较和宽松相等中遵循不同规则⁠:

      console.log(null == 0); // false
      console.log(null >= 0); // true
      
      console.log(undefined > 0);  // false
      console.log(undefined <= 0); // false

      不要通过这类边界行为表达业务逻辑⁠。输入数据应先验证并转换为明确类型⁠。

      1.8.5 逻辑运算符

      逻辑或 || 返回第一个真值⁠;如果所有操作数都为假值⁠,则返回最后一个操作数⁠:

      console.log("" || "default"); // default
      console.log("value" || "default"); // value

      逻辑与 && 返回第一个假值⁠;如果所有操作数都为真值⁠,则返回最后一个操作数⁠:

      console.log(0 && "next"); // 0
      console.log(1 && "next"); // next

      逻辑非 ! 先执行布尔转换再取反⁠:

      console.log(!0);   // true
      console.log(!"");  // true
      console.log(!"0"); // false

      双重逻辑非可以转换为布尔值⁠:

      console.log(!!"text"); // true

      这些运算符都会短路⁠。确定结果后⁠,右侧表达式不会继续执行⁠:

      const user = null;
      
      user && console.log(user.name);

      1.8.6 空值合并运算符

      ?? 只在左侧为 null 或 undefined 时使用右侧值⁠:

      console.log(0 ?? 10);       // 0
      console.log("" ?? "text");  // ""
      console.log(false ?? true); // false
      console.log(null ?? 10);    // 10

      与 || 相比⁠,?? 不会把 0⁠、空字符串和 false 当作缺失值⁠。

      ?? 不能在没有圆括号的情况下直接与 && 或 || 混合⁠:

      // SyntaxError
      // const result = a || b ?? c;
      
      const result = (a || b) ?? c;

      1.9 条件分支

      1.9.1 if 语句

      if 会对条件表达式执行布尔转换⁠:

      const score = 85;
      
      if (score >= 90) {
        console.log("A");
      } else if (score >= 80) {
        console.log("B");
      } else {
        console.log("C");
      }

      即使分支中只有一条语句⁠,也推荐保留花括号⁠,以减少修改代码时引入控制流错误的风险⁠。

      1.9.2 条件运算符

      条件运算符是一个表达式⁠,会返回两个候选表达式之一⁠:

      const age = 18;
      const category = age >= 18 ? "adult" : "minor";

      它适合根据简单条件选择一个值⁠。复杂分支或包含大量副作用的逻辑应使用 if⁠,避免嵌套三元表达式降低可读性⁠。

      1.9.3 switch 语句

      switch 适合针对一个表达式进行多路匹配⁠:

      const status = "pending";
      
      switch (status) {
        case "pending":
          console.log("Waiting");
          break;
      
        case "fulfilled":
          console.log("Completed");
          break;
      
        case "rejected":
          console.log("Failed");
          break;
      
        default:
          console.log("Unknown status");
      }

      case 匹配采用严格相等语义⁠:

      const value = "2";
      
      switch (value) {
        case 2:
          console.log("number");
          break;
      
        case "2":
          console.log("string");
          break;
      }

      输出为⁠:

      string

      如果匹配分支没有通过 break⁠、return 或 throw 等方式终止⁠,控制流会继续执行后续分支⁠,这称为穿透(⁠Fall-through⁠)⁠:

      const role = "editor";
      
      switch (role) {
        case "admin":
        case "editor":
          console.log("Can edit");
          break;
      
        default:
          console.log("Read only");
      }

      有意使用穿透时⁠,应让代码结构清晰⁠,必要时添加简短注释⁠。

      1.10 循环与迭代

      1.10.1 while

      while 在每轮开始前检查条件⁠:

      let count = 0;
      
      while (count < 3) {
        console.log(count);
        count += 1;
      }

      如果条件初始就是假值⁠,循环体不会执行⁠。

      1.10.2 do…while

      do...while 先执行循环体⁠,再检查条件⁠,因此至少执行一次⁠:

      let count = 0;
      
      do {
        console.log(count);
        count += 1;
      } while (count < 3);

      do...while 末尾通常保留分号⁠。

      1.10.3 for

      for 适合具有初始化⁠、继续条件和步进逻辑的循环⁠:

      for (let index = 0; index < 3; index += 1) {
        console.log(index);
      }

      三个部分都可以省略⁠:

      let count = 0;
      
      for (;;) {
        if (count >= 3) {
          break;
        }
      
        count += 1;
      }

      使用 let 声明循环变量时⁠,规范为每轮迭代提供适当的词法绑定语义⁠,使闭包可以捕获对应轮次的值⁠:

      const callbacks = [];
      
      for (let index = 0; index < 3; index += 1) {
        callbacks.push(() => index);
      }
      
      console.log(callbacks[0]()); // 0
      console.log(callbacks[1]()); // 1
      console.log(callbacks[2]()); // 2

      闭包机制将在第二章详细讨论⁠。

      1.10.4 break 与 continue

      break 立即终止当前循环⁠:

      for (let index = 0; index < 10; index += 1) {
        if (index === 3) {
          break;
        }
      
        console.log(index);
      }

      continue 跳过当前轮剩余代码⁠,进入下一轮⁠:

      for (let index = 0; index < 5; index += 1) {
        if (index % 2 === 0) {
          continue;
        }
      
        console.log(index);
      }

      在 for 循环中⁠,执行 continue 后会先运行步进表达式⁠,再检查下一轮条件⁠。

      1.10.5 标签语句

      标签可以与 break 或 continue 配合控制外层循环⁠:

      outerLoop:
      for (let row = 0; row < 3; row += 1) {
        for (let column = 0; column < 3; column += 1) {
          if (row === 1 && column === 1) {
            break outerLoop;
          }
      
          console.log(row, column);
        }
      }

      break outerLoop 会终止被标记的外层循环⁠。

      标签不是函数跳转机制⁠,也不能跨越函数边界⁠。它适合少量明确的嵌套控制流⁠;如果标签过多⁠,通常应通过函数拆分或重新组织算法降低复杂度⁠。

      1.10.6 for…in

      for...in 遍历对象自身及其原型链上的可枚举字符串属性名⁠:

      const user = {
        name: "Alice",
        score: 95,
      };
      
      for (const key in user) {
        if (Object.hasOwn(user, key)) {
          console.log(key, user[key]);
        }
      }

      for...in 得到的是属性名⁠,而不是属性值⁠。

      不推荐使用 for...in 遍历数组⁠:

      const values = [10, 20, 30];
      
      // 不推荐
      for (const key in values) {
        console.log(key);
      }

      原因包括⁠:

      • 得到的是字符串属性名⁠;
      • 可能包含额外可枚举属性⁠;
      • 它表达的是对象属性枚举⁠,而不是数组元素迭代⁠。

      1.10.7 for…of

      for...of 通过可迭代协议读取值⁠:

      const values = [10, 20, 30];
      
      for (const value of values) {
        console.log(value);
      }

      字符串⁠、数组⁠、Map⁠、Set 和类型化数组等内置对象可直接迭代⁠。

      普通对象默认不可迭代⁠:

      const user = {
        name: "Alice",
      };
      
      // TypeError
      // for (const value of user) {
      //   console.log(value);
      // }

      可以先使用对象静态方法取得数组⁠:

      for (const [key, value] of Object.entries(user)) {
        console.log(key, value);
      }

      迭代器协议将在第七章进一步讨论⁠。

      1.11 位运算符

      JavaScript 的普通数字位运算会先把 Number 转换为 32 位整数⁠,再执行二进制运算⁠。运算完成后⁠,结果再以 Number 返回⁠。

      因此⁠,位运算适合处理 32 位标志位⁠、掩码和特定底层数据⁠,不适合替代一般数值运算⁠,也不应仅以“⁠更高效⁠”为理由使用⁠。

      1.11.1 常用位运算符

      运算符名称示例
      &按位与a & b
      ``按位或
      ^按位异或a ^ b
      ~按位非~a
      <<左移a << n
      >>有符号右移a >> n
      >>>无符号右移a >>> n

      1.11.2 按位与⁠、或和异或

      const first = 0b1100;
      const second = 0b1010;
      
      console.log((first & second).toString(2)); // "1000"
      console.log((first | second).toString(2)); // "1110"
      console.log((first ^ second).toString(2)); // "110"

      按位与只保留两边都为 1 的位⁠;按位或保留任一边为 1 的位⁠;按位异或保留两边不同的位⁠。

      1.11.3 标志位

      可以使用不同二进制位表示多个布尔权限⁠:

      const READ = 1 << 0;   // 0001
      const WRITE = 1 << 1;  // 0010
      const DELETE = 1 << 2; // 0100
      
      let permissions = READ | WRITE;
      
      console.log((permissions & READ) !== 0);   // true
      console.log((permissions & DELETE) !== 0); // false
      
      permissions |= DELETE;
      
      console.log((permissions & DELETE) !== 0); // true

      清除标志位⁠:

      permissions &= ~WRITE;
      
      console.log((permissions & WRITE) !== 0); // false

      1.11.4 移位

      console.log(5 << 1); // 10
      console.log(8 >> 1); // 4

      左移和右移有时类似乘除 2n2^n2n⁠,但它们会先发生 32 位整数转换⁠,溢出⁠、符号位和截断行为与普通算术不同⁠。

      console.log(2 ** 31);      // 2147483648
      console.log((2 ** 31) | 0); // -2147483648

      >>> 使用零填充右移⁠,并把结果视为无符号 32 位模式⁠:

      console.log(-1 >>> 0); // 4294967295

      它常被用于把一个数值转换为无符号 32 位结果⁠,但会截断超出 32 位的部分⁠:

      console.log(2 ** 40 >>> 0); // 0

      因此⁠,不能把 >>> 1 普遍视为比 Math.floor(value / 2) 更安全的除二方法⁠,也不应在可能超过 32 位范围的数组索引计算中使用它⁠。

      1.11.5 BigInt 位运算

      BigInt 支持⁠:

      • &⁠;
      • |⁠;
      • ^⁠;
      • ~⁠;
      • <<⁠;
      • >>⁠。

      但不支持无符号右移 >>>⁠,因为 BigInt 没有固定宽度的最高符号位⁠:

      console.log(8n >> 1n); // 4n
      
      // TypeError
      // console.log(8n >>> 1n);

      1.12 浏览器中的基础交互

      浏览器提供 alert()⁠、prompt() 和 confirm() 等模态对话框⁠。它们属于 Web API⁠,而不是 ECMAScript 语言本身⁠。

      alert("Hello");
      
      const name = prompt("Your name:", "Alice");
      const confirmed = confirm("Continue?");

      典型返回值为⁠:

      • alert() 返回 undefined⁠;
      • prompt() 在确认时返回字符串⁠,在取消时返回 null⁠;
      • confirm() 返回布尔值⁠。

      这些对话框会阻断当前页面的交互与脚本推进⁠,而且浏览器可以对其显示方式和可用性施加限制⁠。现代界面通常使用 HTML⁠、CSS 和事件处理器构建非阻断式交互⁠。

      调试输出通常使用⁠:

      console.log("message");
      console.warn("warning");
      console.error("error");

      console 同样由宿主环境提供⁠,其显示格式不属于 ECMAScript 规范保证⁠。

      1.13 本章小结

      本章建立了后续学习所需的语言基础⁠:

      1. ECMAScript 定义语言语义⁠,浏览器和 Node.js 提供宿主 API⁠,引擎负责具体实现⁠;
      2. 规范中的环境记录⁠、引用和内部槽是行为模型⁠,不等同于固定物理内存结构⁠;
      3. let 和 const 具有块级作用域与暂时性死区⁠,const 限制的是绑定重新赋值⁠;
      4. JavaScript 包含 7 种原始类型和对象类型⁠;
      5. Number 具有安全整数范围⁠、浮点误差⁠、Infinity 和 NaN 等边界⁠;
      6. typeof null 是保留至今的历史行为⁠,不能用于判断空值⁠;
      7. 隐式类型转换取决于具体运算符⁠,二元 + 同时支持数值加法和字符串拼接⁠;
      8. 严格相等通常比宽松相等更容易推理⁠;
      9. &&⁠、|| 和 ?? 返回操作数并具有短路求值语义⁠;
      10. for...in 枚举属性名⁠,for...of 迭代可迭代对象的值⁠;
      11. 普通数值位运算采用 32 位整数语义⁠,可能发生截断和符号变化⁠;
      12. ASI 是语法规则的一部分⁠,不能简单理解为在每个换行处自动补分号⁠。

      参考资料

      1. javascript.info: JavaScript Fundamentals
      2. MDN JavaScript Guide: Grammar and types
      3. MDN JavaScript Guide: Expressions and operators
      4. MDN JavaScript Guide: Control flow and error handling
      5. MDN JavaScript Guide: Loops and iteration
      6. MDN JavaScript Reference: Strict mode
      7. MDN JavaScript Reference: typeof
      8. ECMAScript 2026 Language Specification: Lexical Grammar
      9. ECMAScript 2026 Language Specification: ECMAScript Data Types and Values
      10. ECMAScript 2026 Language Specification: ECMAScript Language—Expressions
      11. ECMAScript 2026 Language Specification: Statements and Declarations
      12. HTML Standard: Simple dialogs
      上一篇 下一篇 JavaScript 2. 函数 2026 年 7 月 11 日
      © 2026 CHEN Hua All rights reserved
      闽ICP备2026003335号 · 粤公网安备44030002014022号
      © Hua Chen / PhysChen.com