2026-01-14    2026-01-14    3393 字  7 分钟

一直没有静下心来,打扫 JavaScript 的边边角角,慢慢干吧~

第一部分 JavaScript 编程语言

什么是 JavaScript?

一种可以直接写在网页 HTML 中的脚本语言,在页面加载的时候自动执行。脚本以纯文本的形式提供,不需要编译,由 JavaScript 引擎直接解释运行。

引擎是如何工作的呢? 引擎很复杂,原理很简单:

  1. 引擎(如果是浏览器,则引擎被嵌入在其中)读取(“解析”)脚本;
  2. 然后,引擎将脚本转化(“编译”)为机器语言;
  3. 然后,机器代码快速地执行。

引擎会对流程中的每个阶段都进行优化。它甚至可以在编译的脚本运行时监视它,分析流经该脚本的数据,并根据获得的信息进一步优化机器代码。

JavaScript 的能力很大程度上取决于它运行的宿主环境(浏览器、Node),其本身没有直接访问操作系统的功能。

规范:ECMAScript® 2026 Language Specification
手册:JavaScript 参考 - JavaScript | MDN

我们几乎可以使用 <script> 标签将 JavaScript 程序插入到 HTML 文档的任何位置。

数据类型

在 JavaScript 中有 8 种基本的数据类型(7 种原始类型和 1 种引用类型)。

原始类型:其值只包含一个单独的内容(字符串、数字或者其他)
Number     —— 代表整数和浮点数,常规数字、Infinity、-Infinity、NaN
BigInt     —— 尾部 n 表示
String     —— 字符串
Boolean    —— true、false
null       —— 空
undefined  —— 通常用作未进行初始化时的默认值
Symbol     —— 用于创建对象的唯一标示符

引用类型:用于储存数据集合和更复杂的实体
Object

我们可以通过 typeof 运算符 查看存储在变量中的数据类型(以字符串的形式返回)。

注意:typeof 是一个运算符,而不是函数!通常使用 typeof x 这种形式,有时也会使用 typeof(x) —— 但这里的括号只是数学运算分组的括号。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
typeof undefined    // "undefined"
typeof 0            // "number"
typeof 10n          // "bigint"
typeof true         // "boolean"
typeof "foo"        // "string"
typeof Symbol("id") // "symbol"

typeof Math         // "object"    (1)
typeof null         // "object"    (2)
typeof alert        // "function"  (3)

Math 是一个提供数学运算的内建 object 。

typeof null 的结果为什么是 "object" ?不应该是 null 吗?这是官方承认的 typeof 的错误,为了兼容性保留下来的。

typeof alert 的结果为什么是 "function" ?并没有这个类型啊。原来是 typeof 会对函数区分对待,也是来自于 JavaScript 早期的问题,尽管这种行为原格来说是不正确的,但在实际编程中却非常方便。

大多数情况下,运算符和函数会自动将赋予它们的值转换为正确的类型。 如,alert 会自动将任何值都转换为字符串进行显示,算术运算符会将值转换为数字。

有三种常用的显示类型转换 —— String(value)、 Number(value) 、 Boolean(value) 。

其中需要注意的是, undefined 进行数字转换时输出为 NaN,而非 0 ;null 转换为 0 ;布尔转换时,对 "0" 和只有空格的字符串 " " 输出结果为 true 。

基础运算

算数运算中,+ 是比较特殊的,既可用于求和,又用于字符串的拼接 —— 只要任意一个运算元是字符串,另一个运算元也会被转成字符串。

二元 + 是唯一一个以这种方式支持字符串的运算符。 其他算术运算符只对数字起作用,并且总是将其运算元转换为数字。

+ 号还可以作为一元运算符使用,作用于单个值 —— 会将非数字的运算元转换为数字,效果和 Number(...) 相同。

1
2
3
4
5
6
// 运算符是按顺序工作的
alert(2 + 2 + '1')	// → '41'
alert('1' + 2 + 2)	// → '122'
// 非数字值转换为数字
alert(+true)		// → 1
alert(+'')			// → 0

在 JavaScript 中,所有运算符都会返回一个值。 = 就是赋值运算符,它也会返回一个值。

自增、自减 —— 初学者的必经之罪???

:: 其实是因为大多数垃圾教材只描述了结果,没有介绍原理。

自增/自减只能应用于变量,可以置于变量前,也可以置于变量后。我们以自增 ++ 为例,看一下“前置、后置”的区别。

¹ 如果自增、自减的值不会被使用,那么两者形式没有区别:

1
2
3
4
let counter = 0;
counter++;
++counter;
alert(counter);	// → 2

² 如果我们想要对变量进行自增操作,并且 需要立刻使用自增后的值,使用前置;如果想使用其自增之前的值,使用后置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let counter_front = 0;
let counter_f = ++counter_front;
// —— 等价过程 ——
// counter_front = counter_front + 1
// let counter_f = counter_font
alert(counter_f);	// → 1

let counter_end = 0;
let counter_e = counter_end++;
// —— 等价过程 ——
// let counter_e = counter_end
// counter_end = counter_end + 1
alert(counter_e);	// → 0

什么? 逗号也是运算符?

是的,逗号运算符能让我们处理多个表达式,使用 , 将它们分开。每个表达式都运行了,但是只有最后一个结果会被返回。

但要注意, 逗号运算符的优先级非常低! 比 = 号还低!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let a = (1 + 2, 3 + 4);
alert(a);	// → 7

let b = 1 + 2, 3 + 4;
alert(b);	// → 3
// 相当于 let (b = 1 + 2), 3 + 4;
// 执行过程如下:
// → a = 3, 7
// → a = 3
// → 逗号之后的数值 7 不会再执行,被忽略掉了

值的比较

所有比较运算符均返回布尔值。

当不同类型的值进行比较时,JavaScript 会首先将其转化为数字再判定大小。

需要注意的是,相等判断符号 == 两侧的值会优先转化为数字(除了 null 和 undefined),但 严格相等运算符 === 在进行比较时不会做任何的类型转换 。

其中,字符串就按字符逐个进行比较的 —— 按照字符的 Unicode 编码顺序比较。

呃,再来看看不让人省心的 null 和 undefined 。

null 和 undefined 在相等性检查 == 中不会进行任何的类型转换,它们有自己独立的比较规则 —— 除了它们之间互等之外,不会等于任何其他的值。

1
2
3
4
5
6
alert(null === undefined);	// → false 类型不同
alert(null == undefined);	// → true
alert(null == 0);			// → false 不转换
alert(null >= 0);			// → true  会转换
alert(undefined == 0);		// → false 不转换
alert(undefined >= 0);		// → false NaN

undefined 更特立独行,它在比较中被转换成了 NaN,它与任何值进行比较都会返回 false。

所以,在日常应用中,除了严格相等 === 外,其他但凡是有 undefined/null 参与的比较,我们需要格外小心。对于取值可能是 undefined/null 的变量,按需要分别检查它的取值情况。

条件分支

if (…) 语句会计算圆括号内的表达式,并将计算结果转换为布尔型。数字 0、空字符串 ""、null、undefined 和 NaN 都会被转换成 false,其他皆为真。

有时我们需要测试一个条件的几个变体。我们可以通过使用 else if 子句实现。如果前面的条件不符合,就会转到下一个条件,直到最后的 else (当然它是可选的)。

switch 语句为多分支选择的情况提供了一个更具描述性的方式,他它有至少一个 case 代码块和一个可选的 default 代码块。 其中,需要注意的是 case 后的匹配与 switch 中的传参的相等是 严格相等。

逻辑运算符

JavaScript 中有四个逻较运算符:|| 或、 && 与、 ! 非、 ?? 空值合并运算符。

虽然它们被称为逻辑运算符,但这些运算可以被应用于任意类型的值,而不仅仅是布尔值。它们的结果也同样可以是任意类型。

¹ || 或运算寻找第一个真值。

1
result = value1 || value2 || value3;

或运算符 || 做了如下事情:

  • 从左到右依次计算操作数;
  • 处理每一个操作数时,都将其转化为布尔值。如果结果是 true,就停止计算,返回这个操作数的初始值;
  • 如果所有的操作数都被计算过(都为 false),则返回最后一个操作数。

换句话说,一个或运算 || 的链,将返回第一个真值,如果不存在真值,就返回该链的最后一个值。

我们称上述这种方式为 短路求值(Short-circuit evaluation) 。常用来为变量设置默认值。

² && 与运算寻找第一个假值 —— 与运算返回第一个假值,如果没有假值就返回最后一个值。它的优先级比 || 高。

³ ! 非运算符接受一个参数,并返回相反的值。!! 常用来转化一个任意值为布尔值,等价于 Boolen(x) 。

⁴ 空值合并运算符 ?? 是什么呢? 它还什么用呢?

1
2
3
result = a ?? b
// 等价于
// ressult = (a !== null && a !== undefined) ? a : b;

也就是说,如果第一个参数不是 null/undefined,则 ?? 返回第一个参数。否则,返回第二个参数。

它也用来设置默认值,但与 || 不同的是,它只是不允许值为 null/undefined ,但是允许其为 false、 0 、空字符串 '' 。

以上就是 ?? 出现的原因。

运算优先级: ! > && > || = ?? 。

注意:出于安全原因,JavaScript 禁止将 ?? 运算符与 && 和 || 运算符一起使用,除非使用括号明确指定了优先级。强用,会报错。

1
2
let x = 1 && 2 ?? 3;	// 语法错误
let y = (1 && 2) ?? 3;	// 正常运行

循环

循环 是一种重复运行同一代码的方法。

本文涵盖了基础的循环:while 、 do...while 和 for(..; ..; ..) 。

其他类型循环会放在以后的章节:

  • 用于遍历对象属性的 for...in 循环;
  • 用于遍历数组和可迭代对象的 for...of 和 iterables 循环。

我们来看看最常用的 for 循环:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// for (let i = 0; i < 3; i++) alert(i)

// 开始
let i = 0
// 如果条件为真,运行下一步
if (i < 3) { alert(i); i++ }
// 如果条件为真,运行下一步
if (i < 3) { alert(i); i++ }
// 如果条件为真,运行下一步
if (i < 3) { alert(i); i++ }
// ……结束,因为现在 i == 3

这里“计数”变量 i 是在循环中声明的,这叫做“内联”变量声明 —— 其作用域只存在于循环体内。

通常条件为假时,循环会终止,但我们可以使用 break 指令强止退出。也可以使用 continue 停止当前这一次迭代,并强制启到新一轮循环。

多层嵌套循环中,break 和 continue 都只能跳出其所在的循环,那么,当我们需要一次从多层循环中跳出来,怎么办呢?标签!

标签 是在循环之前带有冒号的标识符:

labelName: for (...) {
  ...
}

然后,使用 break <labelName> 语句跳出循环至标签处。