2026-01-14    2026-01-19    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,而非 0null 转换为 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 会首先将其转化为数字再判定大小。

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

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

呃,再来看看不让人省心的 nullundefined

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

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、空字符串 ""nullundefined 和 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 ,但是允许其为 false0 、空字符串 ''

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

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

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

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

循环

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

本文涵盖了基础的循环:whiledo...whilefor(..; ..; ..)

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

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

我们来看看最常用的 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 停止当前这一次迭代,并强制启到新一轮循环。

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

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

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

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