JavaScript 权威指南第七版(GPT 重译)(一)(2)https://developer.aliyun.com/article/1485287
在 JavaScript 中,非数字值具有一个不寻常的特征:它与任何其他值(包括自身)都不相等。这意味着您不能写 x === NaN
来确定变量 x
的值是否为 NaN
。相反,您必须写 x != x
或 Number.isNaN(x)
。只有当 x
的值与全局常量 NaN
相同时,这些表达式才为真。
全局函数 isNaN()
类似于 Number.isNaN()
。如果其参数是 NaN
,或者该参数是无法转换为数字的非数字值,则返回 true
。相关函数 Number.isFinite()
如果其参数是除 NaN
、Infinity
或 -Infinity
之外的数字,则返回 true
。全局函数 isFinite()
如果其参数是有限数字或可以转换为有限数字,则返回 true
。
负零值也有些不寻常。它与正零相等(即使使用 JavaScript 的严格相等测试),这意味着这两个值几乎无法区分,除非用作除数:
let zero = 0; // Regular zero let negz = -0; // Negative zero zero === negz // => true: zero and negative zero are equal 1/zero === 1/negz // => false: Infinity and -Infinity are not equal
3.2.4 二进制浮点数和舍入误差
实数有无限多个,但只有有限数量的实数(准确地说是 18,437,736,874,454,810,627)可以被 JavaScript 浮点格式精确表示。这意味着当您在 JavaScript 中使用实数时,该数字的表示通常是实际数字的近似值。
JavaScript 使用的 IEEE-754 浮点表示法(几乎所有现代编程语言都使用)是二进制表示法,可以精确表示分数如 1/2
、1/8
和 1/1024
。不幸的是,我们最常使用的分数(尤其是在进行财务计算时)是十进制分数:1/10
、1/100
等。二进制浮点表示法无法精确表示像 0.1
这样简单的数字。
JavaScript 数字具有足够的精度,可以非常接近地近似 0.1
。但是,这个数字无法精确表示可能会导致问题。考虑以下代码:
let x = .3 - .2; // thirty cents minus 20 cents let y = .2 - .1; // twenty cents minus 10 cents x === y // => false: the two values are not the same! x === .1 // => false: .3-.2 is not equal to .1 y === .1 // => true: .2-.1 is equal to .1
由于四舍五入误差,.3 和 .2 的近似值之间的差异并不完全等同于 .2 和 .1 的近似值之间的差异。重要的是要理解这个问题并不特定于 JavaScript:它影响任何使用二进制浮点数的编程语言。此外,请注意代码中的值 x
和 y
非常接近彼此和正确值。计算出的值对于几乎任何目的都是足够的;问题只在我们尝试比较相等值时才会出现。
如果这些浮点数近似值对您的程序有问题,请考虑使用缩放整数。例如,您可以将货币值作为整数分而不是小数美元进行操作。
3.2.5 使用 BigInt 进行任意精度整数运算
JavaScript 的最新特性之一,定义在 ES2020 中,是一种称为 BigInt 的新数值类型。截至 2020 年初,它已经在 Chrome、Firefox、Edge 和 Node 中实现,并且 Safari 中正在进行实现。顾名思义,BigInt 是一个数值类型,其值为整数。JavaScript 主要添加了这种类型,以允许表示 64 位整数,这对于与许多其他编程语言和 API 兼容是必需的。但是 BigInt 值可以有数千甚至数百万位数字,如果你需要处理如此大的数字的话。(但是请注意,BigInt 实现不适用于加密,因为它们不会尝试防止时间攻击。)
BigInt 字面量写为一个由数字组成的字符串,后面跟着一个小写字母 n
。默认情况下,它们是以 10 进制表示的,但你可以使用 0b
、0o
和 0x
前缀来表示二进制、八进制和十六进制的 BigInt:
1234n // A not-so-big BigInt literal 0b111111n // A binary BigInt 0o7777n // An octal BigInt 0x8000000000000000n // => 2n**63n: A 64-bit integer
你可以将 BigInt()
作为一个函数,用于将常规的 JavaScript 数字或字符串转换为 BigInt 值:
BigInt(Number.MAX_SAFE_INTEGER) // => 9007199254740991n let string = "1" + "0".repeat(100); // 1 followed by 100 zeros. BigInt(string) // => 10n**100n: one googol
与 BigInt 值进行算术运算的方式与常规 JavaScript 数字的算术运算类似,只是除法会舍弃任何余数并向下取整(朝着零的方向):
1000n + 2000n // => 3000n 3000n - 2000n // => 1000n 2000n * 3000n // => 6000000n 3000n / 997n // => 3n: the quotient is 3 3000n % 997n // => 9n: and the remainder is 9 (2n ** 131071n) - 1n // A Mersenne prime with 39457 decimal digits
尽管标准的 +
、-
、*
、/
、%
和 **
运算符可以与 BigInt 一起使用,但重要的是要理解,你不能将 BigInt 类型的操作数与常规数字操作数混合使用。这一开始可能看起来令人困惑,但这是有充分理由的。如果一个数值类型比另一个更通用,那么可以很容易地定义混合操作数的算术运算,只需返回更通用类型的值。但是没有一个类型比另一个更通用:BigInt 可以表示非常大的值,使其比常规数字更通用。但 BigInt 只能表示整数,使得常规的 JavaScript 数字类型更通用。这个问题没有解决的方法,所以 JavaScript 通过简单地不允许混合操作数来绕过它。
相比之下,比较运算符可以处理混合数值类型(但请参阅 §3.9.1 了解有关 ==
和 ===
之间差异的更多信息):
1 < 2n // => true 2 > 1n // => true 0 == 0n // => true 0 === 0n // => false: the === checks for type equality as well
位运算符(在 §4.8.3 中描述)通常与 BigInt 操作数一起使用。然而,Math
对象的函数都不接受 BigInt 操作数。
3.2.6 日期和时间
JavaScript 定义了一个简单的 Date 类来表示和操作表示日期和时间的数字。JavaScript 的日期是对象,但它们也有一个数值表示作为 时间戳,指定自 1970 年 1 月 1 日以来经过的毫秒数:
let timestamp = Date.now(); // The current time as a timestamp (a number). let now = new Date(); // The current time as a Date object. let ms = now.getTime(); // Convert to a millisecond timestamp. let iso = now.toISOString(); // Convert to a string in standard format.
Date 类及其方法在 §11.4 中有详细介绍。但是我们将在 §3.9.3 中再次看到 Date 对象,当我们检查 JavaScript 类型转换的细节时。
3.3 文本
用于表示文本的 JavaScript 类型是 字符串。字符串是一个不可变的有序 16 位值序列,其中每个值通常表示一个 Unicode 字符。字符串的 长度 是它包含的 16 位值的数量。JavaScript 的字符串(以及其数组)使用从零开始的索引:第一个 16 位值位于位置 0,第二个位于位置 1,依此类推。空字符串 是长度为 0 的字符串。JavaScript 没有一个特殊的类型来表示字符串的单个元素。要表示一个单个的 16 位值,只需使用长度为 1 的字符串。
3.3.1 字符串字面量
要在 JavaScript 程序中包含一个字符串,只需将字符串的字符置于匹配的一对单引号、双引号或反引号中('
或 "
或 `
)。双引号字符和反斜线可能包含在由单引号字符分隔的字符串中,由双引号和反斜线分隔的字符串也是如此。以下是字符串文字的示例:
"" // 空字符串:它没有任何字符 'testing' "3.14" 'name="myform"' "Wouldn't you prefer O'Reilly's book?" "τ is the ratio of a circle's circumference to its radius" `"She said ''hi''", he said.`
使用反引号界定的字符串是 ES6 的一个特性,允许将 JavaScript 表达式嵌入到字符串字面量中(或 插入 到其中)。这种表达式插值语法在 §3.3.4 中有介绍。
JavaScript 的原始版本要求字符串字面量写在单行上,通常会看到 JavaScript 代码通过使用 +
运算符连接单行字符串来创建长字符串。然而,从 ES5 开始,你可以通过在每行的末尾(除了最后一行)加上反斜杠(\
)来跨多行书写字符串字面量。反斜杠和其后的换行符不属于字符串字面量的一部分。如果需要在单引号或双引号字符串字面量中包含换行符,可以使用字符序列 \n
(在下一节中有介绍)。ES6 的反引号语法允许字符串跨多行书写,此时换行符属于字符串字面量的一部分:
// 一个表示在一行上写的 2 行的字符串: 'two\nlines' "one\ long\ line" // 两行字符串分别写在两行上: `the newline character at the end of this line is included literally in this string`
请注意,当使用单引号界定字符串时,必须小心处理英语缩写和所有格,例如 can’t 和 O’Reilly’s。由于撇号与单引号字符相同,必须使用反斜杠字符(\
)来“转义”出现在单引号字符串中的任何撇号(转义在下一节中有解释)。
在客户端 JavaScript 编程中,JavaScript 代码可能包含 HTML 代码的字符串,而 HTML 代码可能包含 JavaScript 代码的字符串。与 JavaScript 一样,HTML 使用单引号或双引号来界定其字符串。因此,在结合 JavaScript 和 HTML 时,最好使用一种引号风格用于 JavaScript,另一种引号风格用于 HTML。在下面的示例中,“Thank you” 字符串在 JavaScript 表达式中使用单引号引起,然后在 HTML 事件处理程序属性中使用双引号引起:
<button onclick="alert('Thank you')">Click Me</button>
3.3.2 字符串字面量中的转义序列
反斜杠字符(\
)在 JavaScript 字符串中有特殊用途。与其后的字符结合,它表示字符串中无法用其他方式表示的字符。例如,\n
是表示换行字符的 转义序列。
另一个之前提到的例子是 \'
转义,表示单引号(或撇号)字符。当需要在包含在单引号中的字符串字面量中包含撇号时,这个转义序列很有用。你可以看到为什么这些被称为转义序列:反斜杠允许你从单引号字符的通常解释中逃脱。你不再使用它来标记字符串的结束,而是将其用作撇号:
'You\'re right, it can\'t be a quote'
表 3-1 列出了 JavaScript 转义序列及其表示的字符。三个转义序列是通用的,可以通过指定其 Unicode 字符代码作为十六进制数来表示任何字符。例如,序列 \xA9
表示版权符号,其 Unicode 编码由十六进制数 A9 给出。类似地,\u
转义表示由四个十六进制数字或在大括号中括起的一到五个数字指定的任意 Unicode 字符:例如,\u03c0
表示字符 π,而 \u{1f600}
表示“笑脸”表情符号。
表 3-1. JavaScript 转义序列
序列 | 表示的字符 |
\0 |
NUL 字符 (\u0000 ) |
\b |
退格符 (\u0008 ) |
\t |
水平制表符 (\u0009 ) |
\n |
换行符 (\u000A ) |
\v |
垂直制表符 (\u000B ) |
\f |
换页符 (\u000C ) |
\r |
回车符 (\u000D ) |
\" |
双引号 (\u0022 ) |
\' |
撇号或单引号 (\u0027 ) |
\\ |
反斜杠 (\u005C ) |
\x nn |
由两个十六进制数字 nn 指定的 Unicode 字符 |
\u nnnn |
由四个十六进制数字 nnnn 指定的 Unicode 字符 |
\u{ n} |
由代码点 n 指定的 Unicode 字符,其中 n 是 0 到 10FFFF 之间的一到六个十六进制数字(ES6) |
如果 \
字符位于除表 3-1 中显示的字符之外的任何字符之前,则反斜杠将被简单地忽略(尽管语言的未来版本当然可以定义新的转义序列)。例如,\#
与 #
相同。最后,正如前面提到的,ES5 允许在换行符之前放置反斜杠,以便跨多行断开字符串文字。
3.3.3 处理字符串
JavaScript 的内置功能之一是能够连接字符串。如果您使用 +
运算符与数字一起使用,它们会相加。但是如果您在字符串上使用此运算符,则会通过将第二个字符串附加到第一个字符串来连接它们。例如:
let msg = "Hello, " + "world"; // 生成字符串 "Hello, world" let greeting = "Welcome to my blog," + " " + name;
字符串可以使用标准的 ===
相等和 !==
不等运算符进行比较:只有当它们由完全相同的 16 位值序列组成时,两个字符串才相等。字符串也可以使用 <
、<=
、>
和 >=
运算符进行比较。字符串比较只是简单地比较 16 位值。(有关更健壮的区域感知字符串比较和排序,请参见 §11.7.3。)
要确定字符串的长度——它包含的 16 位值的数量——请使用字符串的 length
属性:
s.length
除了 length
属性之外,JavaScript 还提供了丰富的 API 用于处理字符串:
let s = "Hello, world"; // 以一些文本开头。 // 获取字符串的部分 s.substring(1,4) // => "ell": 第 2、3、4 个字符。 s.slice(1,4) // => "ell": 同上 s.slice(-3) // => "rld": 最后 3 个字符 s.split(", ") // => ["Hello", "world"]: 在分隔符字符串处分割 // 搜索字符串 s.indexOf("l") // => 2: 第一个字母 l 的位置 s.indexOf("l", 3) // => 3: 第一个 "l" 在或之后 3 的位置 s.indexOf("zz") // => -1: s 不包含子字符串 "zz" s.lastIndexOf("l") // => 10: 最后一个字母 l 的位置 // ES6 及更高版本中的布尔搜索函数 s.startsWith("Hell") // => true: 字符串以这些开头 s.endsWith("!") // => false: s 不以此结尾 s.includes("or") // => true: s 包含子字符串 "or" // 创建字符串的修改版本 s.replace("llo", "ya") // => "Heya, world" s.toLowerCase() // => "hello, world" s.toUpperCase() // => "HELLO, WORLD" s.normalize() // Unicode NFC 标准化:ES6 s.normalize("NFD") // NFD 标准化。也可用 "NFKC", "NFKD" // 检查字符串的各个(16 位)字符 s.charAt(0) // => "H": 第一个字符 s.charAt(s.length-1) // => "d": 最后一个字符 s.charCodeAt(0) // => 72: 指定位置的 16 位数字 s.codePointAt(0) // => 72: ES6,适用于大于 16 位的码点 // ES2017 中的字符串填充函数 "x".padStart(3) // => " x": 在左侧添加空格,使长度为 3 "x".padEnd(3) // => "x ": 在右侧添加空格,使长度为 3 "x".padStart(3, "*") // => "**x": 在左侧添加星号,使长度为 3 "x".padEnd(3, "-") // => "x--": 在右侧添加破折号,使长度为 3 // 修剪空格函数。trim() 是 ES5;其他是 ES2019 " test ".trim() // => "test": 删除开头和结尾的空格 " test ".trimStart() // => "test ": 删除左侧的空格。也可用 trimLeft " test ".trimEnd() // => " test": 删除右侧的空格。也可用 trimRight // 其他字符串方法 s.concat("!") // => "Hello, world!": 只需使用 + 运算符 "<>".repeat(5) // => "<><><><><>": 连接 n 个副本。ES6
请记住,在 JavaScript 中字符串是不可变的。像 replace()
和 toUpperCase()
这样的方法会返回新的字符串:它们不会修改调用它们的字符串。
字符串也可以像只读数组一样处理,您可以使用方括号而不是 charAt()
方法从字符串中访问单个字符(16 位值):
let s = "hello, world"; s[0] // => "h" s[s.length-1] // => "d"
3.3.4 模板字面量
在 ES6 及更高版本中,字符串字面量可以用反引号括起来:
let s = `hello world`;
然而,这不仅仅是另一种字符串字面量语法,因为这些模板字面量可以包含任意的 JavaScript 表达式。反引号中的字符串字面量的最终值是通过评估包含的任何表达式,将这些表达式的值转换为字符串,并将这些计算出的字符串与反引号中的文字字符组合而成的:
let name = "Bill"; let greeting = `Hello ${ name }.`; // greeting == "Hello Bill."
${
和匹配的 }
之间的所有内容都被解释为 JavaScript 表达式。花括号外的所有内容都是普通的字符串文字。花括号内的表达式被评估,然后转换为字符串并插入到模板中,替换美元符号、花括号和它们之间的所有内容。
模板字面量可以包含任意数量的表达式。它可以使用任何普通字符串可以使用的转义字符,并且可以跨越任意数量的行,不需要特殊的转义。以下模板字面量包括四个 JavaScript 表达式,一个 Unicode 转义序列,以及至少四个换行符(表达式的值也可能包含换行符):
let errorMessage = `\ # \u2718 Test failure at ${filename}:${linenumber}: ${exception.message} Stack trace: ${exception.stack} `;
这里第一行末尾的反斜杠转义了初始换行符,使得生成的字符串以 Unicode ✘ 字符 (# \u2718
) 开头,而不是一个换行符。
标记模板字面量
模板字面量的一个强大但不常用的特性是,如果一个函数名(或“标签”)紧跟在反引号之前,那么模板字面量中的文本和表达式的值将传递给该函数。标记模板字面量的值是函数的返回值。例如,这可以用来在将值替换到文本之前应用 HTML 或 SQL 转义。
ES6 中有一个内置的标签函数:String.raw()
。它返回反引号内的文本,不处理反斜杠转义:
`\n`.length // => 1: 字符串有一个换行符 String.raw`\n`.length // => 2: 一个反斜杠字符和字母 n
请注意,即使标记模板字面量的标签部分是一个函数,也不需要在其调用中使用括号。在这种非常特殊的情况下,反引号字符替换了开放和关闭括号。
定义自己的模板标签函数的能力是 JavaScript 的一个强大特性。这些函数不需要返回字符串,并且可以像构造函数一样使用,就好像为语言定义了一种新的文字语法。我们将在§14.5 中看到一个例子。
3.3.5 模式匹配
JavaScript 定义了一种称为正则表达式(或 RegExp)的数据类型,用于描述和匹配文本字符串中的模式。RegExps 不是 JavaScript 中的基本数据类型之一,但它们具有类似数字和字符串的文字语法,因此有时似乎是基本的。正则表达式文字的语法复杂,它们定义的 API 也不简单。它们在§11.3 中有详细说明。然而,由于 RegExps 功能强大且常用于文本处理,因此本节提供了简要概述。
一对斜杠之间的文本构成正则表达式文字。在一对斜杠中的第二个斜杠后面也可以跟随一个或多个字母,这些字母修改模式的含义。例如:
/^HTML/; // 匹配字符串开头的字母 H T M L /[1-9][0-9]*/; // 匹配非零数字,后跟任意数量的数字 /\bjavascript\b/i; // 匹配 "javascript" 作为一个单词,不区分大小写
RegExp 对象定义了许多有用的方法,字符串也有接受 RegExp 参数的方法。例如:
let text = "testing: 1, 2, 3"; // 示例文本 let pattern = /\d+/g; // 匹配所有一个或多个数字的实例 pattern.test(text) // => true: 存在匹配项 text.search(pattern) // => 9: 第一个匹配项的位置 text.match(pattern) // => ["1", "2", "3"]: 所有匹配项的数组 text.replace(pattern, "#") // => "testing: #, #, #" text.split(/\D+/) // => ["","1","2","3"]: 以非数字为分隔符进行分割
3.4 布尔值
布尔值表示真或假,开或关,是或否。此类型仅有两个可能的值。保留字true
和false
评估为这两个值。
布尔值通常是您在 JavaScript 程序中进行比较的结果。例如:
a === 4
此代码测试变量a
的值是否等于数字4
。如果是,则此比较的结果是布尔值true
。如果a
不等于4
,则比较的结果是false
。
布尔值通常在 JavaScript 控制结构中使用。例如,JavaScript 中的if/else
语句在布尔值为true
时执行一个操作,在值为false
时执行另一个操作。通常将直接创建布尔值的比较与使用它的语句结合在一起。结果如下:
if (a === 4) { b = b + 1; } else { a = a + 1; }
此代码检查a
是否等于4
。如果是,则将1
添加到b
;否则,将1
添加到a
。
正如我们将在§3.9 中讨论的那样,任何 JavaScript 值都可以转换为布尔值。以下值转换为,并因此像false
一样工作:
undefined null 0 -0 NaN "" // 空字符串
所有其他值,包括所有对象(和数组)转换为,并像true
一样工作。false
和转换为它的六个值有时被称为假值,所有其他值被称为真值。每当 JavaScript 期望布尔值时,假值像false
一样工作,真值像true
一样工作。
例如,假设变量o
可以保存对象或值null
。您可以使用如下if
语句明确测试o
是否非空:
if (o !== null) ...
不等运算符!==
比较o
和null
,并评估为true
或false
。但您可以省略比较,而是依赖于null
为假值,对象为真值的事实:
if (o) ...
在第一种情况下,只有当o
不是null
时,if
的主体才会被执行。第二种情况不那么严格:只有当o
不是false
或任何假值(如null
或undefined
)时,if
的主体才会被执行。哪种if
语句适合你的程序实际上取决于你期望为o
分配什么值。如果你需要区分null
和0
以及""
,那么你应该使用显式比较。
布尔值有一个toString()
方法,你可以用它将它们转换为字符串“true”或“false”,但它们没有其他有用的方法。尽管 API 很简单,但有三个重要的布尔运算符。
&&
运算符执行布尔 AND 操作。只有当它的两个操作数都为真时,它才会评估为真;否则它会评估为假。||
运算符是布尔 OR 操作:如果它的一个(或两个)操作数为真,则它评估为真,如果两个操作数都为假,则它评估为假。最后,一元!
运算符执行布尔 NOT 操作:如果它的操作数为假,则评估为true
,如果它的操作数为真,则评估为false
。例如:
if ((x === 0 && y === 0) || !(z === 0)) { // x 和 y 都为零或 z 非零 }
这些运算符的详细信息在§4.10 中。
3.5 null 和 undefined
null
是一个语言关键字,其值通常用于指示值的缺失。对null
使用typeof
运算符会返回字符串“object”,表明null
可以被视为指示“没有对象”的特殊对象值。然而,在实践中,null
通常被视为其自身类型的唯一成员,并且它可以用于表示数字、字符串以及对象的“无值”。大多数编程语言都有类似 JavaScript 的null
的等价物:你可能熟悉它作为NULL
、nil
或None
。
JavaScript 还有第二个表示值缺失的值。undefined
值代表一种更深层次的缺失。它是未初始化变量的值,以及查询不存在的对象属性或数组元素的值时得到的值。undefined
值也是那些没有显式返回值的函数的返回值,以及没有传递参数的函数参数的值。undefined
是一个预定义的全局常量(不像null
那样是一个语言关键字,尽管在实践中这并不是一个重要的区别),它被初始化为undefined
值。如果你对undefined
值应用typeof
运算符,它会返回undefined
,表明这个值是一个特殊类型的唯一成员。
尽管存在这些差异,null
和undefined
都表示值的缺失,并且通常可以互换使用。相等运算符==
认为它们相等。(使用严格相等运算符===
来区分它们。)它们都是假值:当需要布尔值时,它们的行为类似于false
。null
和undefined
都没有任何属性或方法。实际上,使用.
或[]
来访问这些值的属性或方法会导致 TypeError。
我认为undefined
表示系统级别的、意外的或类似错误的值缺失,而null
表示程序级别的、正常的或预期的值缺失。我尽量避免使用null
和undefined
,但如果需要将这些值分配给变量或属性,或者将这些值传递给函数或从函数中返回这些值,我通常使用null
。一些程序员努力避免使用null
,并在可能的情况下使用undefined
代替。
3.6 符号
在 ES6 中引入了符号作为非字符串属性名称。要理解符号,您需要知道 JavaScript 的基本 Object 类型是一个无序的属性集合,其中每个属性都有一个名称和一个值。属性名称通常(直到 ES6 之前一直)是字符串。但在 ES6 及以后的版本中,符号也可以用于此目的:
let strname = "string name"; // 用作属性名称的字符串 let symname = Symbol("propname"); // 用作属性名称的符号 typeof strname // => "string": strname 是一个字符串 typeof symname // => "symbol": symname 是一个符号 let o = {}; // 创建一个新对象 o[strname] = 1; // 使用字符串名称定义属性 o[symname] = 2; // 使用符号名称定义属性 o[strname] // => 1: 访问以字符串命名的属性 o[symname] // => 2: 访问以符号命名的属性
符号类型没有文字语法。要获得符号值,您需要调用Symbol()
函数。这个函数永远不会两次返回相同的值,即使使用相同的参数调用。这意味着如果您调用Symbol()
来获取一个符号值,您可以安全地将该值用作属性名称,以向对象添加新属性,而不必担心可能会覆盖同名的现有属性。同样,如果使用符号属性名称并且不共享这些符号,您可以确信程序中的其他代码模块不会意外地覆盖您的属性。
在实践中,符号作为一种语言扩展机制。当 ES6 引入了for/of
循环(§5.4.4)和可迭代对象(第十二章)时,需要定义标准方法,使类能够实现自身的可迭代性。但是,标准化任何特定的字符串名称作为此迭代器方法会破坏现有代码,因此使用了一个符号名称。正如我们将在第十二章中看到的,Symbol.iterator
是一个符号值,可以用作方法名称,使对象可迭代。
Symbol()
函数接受一个可选的字符串参数,并返回一个唯一的符号值。如果提供一个字符串参数,那么该字符串将包含在符号的toString()
方法的输出中。但请注意,使用相同的字符串两次调用Symbol()
会产生两个完全不同的符号值。
let s = Symbol("sym_x"); s.toString() // => "Symbol(sym_x)"
toString()
是 Symbol 实例唯一有趣的方法。但是,还有另外两个与 Symbol 相关的函数您应该了解。有时在使用 Symbols 时,您希望将它们私有化,以确保您的属性永远不会与其他代码使用的属性发生冲突。但是,有时您可能希望定义一个 Symbol 值并与其他代码广泛共享。例如,如果您正在定义某种扩展,希望其他代码能够参与其中,那么就会出现这种情况,就像之前描述的Symbol.iterator
机制一样。
为了满足后一种用例,JavaScript 定义了一个全局 Symbol 注册表。Symbol.for()
函数接受一个字符串参数,并返回与您传递的字符串关联的 Symbol 值。如果该字符串尚未关联任何 Symbol,则会创建并返回一个新的 Symbol;否则,将返回已存在的 Symbol。也就是说,Symbol.for()
函数与Symbol()
函数完全不同:Symbol()
永远不会两次返回相同的值,但Symbol.for()
在使用相同字符串调用时总是返回相同的值。传递给Symbol.for()
的字符串将出现在返回的 Symbol 的toString()
输出中,并且还可以通过在返回的 Symbol 上调用Symbol.keyFor()
来检索。
let s = Symbol.for("shared"); let t = Symbol.for("shared"); s === t // => true s.toString() // => "Symbol(shared)" Symbol.keyFor(t) // => "shared"
3.7 全局对象
前面的章节已经解释了 JavaScript 的原始类型和值。对象类型——对象、数组和函数——将在本书的后面章节中单独讨论。但是现在我们必须介绍一个非常重要的对象值。全局对象是一个常规的 JavaScript 对象,具有非常重要的作用:该对象的属性是 JavaScript 程序可用的全局定义标识符。当 JavaScript 解释器启动(或者每当 Web 浏览器加载新页面时),它会创建一个新的全局对象,并赋予它一组初始属性,用于定义:
- 像
undefined
、Infinity
和NaN
这样的全局常量 - 像
isNaN()
、parseInt()
(§3.9.2)和eval()
(§4.12)这样的全局函数 - 像
Date()
、RegExp()
、String()
、Object()
和Array()
(§3.9.2)这样的构造函数 - 像 Math 和 JSON(§6.8)这样的全局对象
全局对象的初始属性不是保留字,但应当视为保留字。本章已经描述了一些这些全局属性。其他大部分属性将在本书的其他地方介绍。
在 Node 中,全局对象有一个名为global
的属性,其值是全局对象本身,因此在 Node 程序中始终可以通过名称global
引用全局对象。
在 Web 浏览器中,Window 对象作为代表浏览器窗口中包含的所有 JavaScript 代码的全局对象。这个全局 Window 对象有一个自引用的window
属性,可以用来引用全局对象。Window 对象定义了核心全局属性,但它还定义了许多其他特定于 Web 浏览器和客户端 JavaScript 的全局对象。Web worker 线程(§15.13)具有与其关联的不同全局对象。工作线程中的代码可以将其全局对象称为self
。
ES2020 最终将globalThis
定义为在任何上下文中引用全局对象的标准方式。截至 2020 年初,这个功能已被所有现代浏览器和 Node 实现。
3.8 不可变的原始值和可变的对象引用
JavaScript 中原始值(undefined
、null
、布尔值、数字和字符串)和对象(包括数组和函数)之间有一个根本的区别。原始值是不可变的:没有办法改变(或“突变”)原始值。对于数字和布尔值来说,这是显而易见的——改变一个数字的值甚至没有意义。然而,对于字符串来说,情况并不那么明显。由于字符串类似于字符数组,您可能希望能够更改任何指定索引处的字符。实际上,JavaScript 不允许这样做,所有看起来返回修改后字符串的字符串方法实际上都是返回一个新的字符串值。例如:
let s = "hello"; // 从一些小写文本开始 s.toUpperCase(); // 返回"HELLO",但不改变 s s // => "hello": 原始字符串没有改变
原始值也是按值比较的:只有当它们的值相同时,两个值才相同。对于数字、布尔值、null
和undefined
来说,这听起来很循环:它们没有其他比较方式。然而,对于字符串来说,情况并不那么明显。如果比较两个不同的字符串值,JavaScript 会将它们视为相等,当且仅当它们的长度相同,并且每个索引处的字符相同。
对象与原始值不同。首先,它们是可变的——它们的值可以改变:
let o = { x: 1 }; // 从一个对象开始 o.x = 2; // 通过更改属性的值来改变它 o.y = 3; // 通过添加新属性再次改变它 let a = [1,2,3]; // 数组也是可变的 a[0] = 0; // 改变数组元素的值 a[3] = 4; // 添加一个新的数组元素
对象不是按值比较的:即使它们具有相同的属性和值,两个不同的对象也不相等。即使它们具有相同顺序的相同元素,两个不同的数组也不相等:
let o = {x: 1}, p = {x: 1}; // 具有相同属性的两个对象 o === p // => false: 不同的对象永远不相等 let a = [], b = []; // 两个不同的空数组 a === b // => false: 不同的数组永远不相等
对象有时被称为引用类型,以区别于 JavaScript 的原始类型。使用这个术语,对象值是引用,我们说对象是按引用比较的:只有当两个对象值引用同一个基础对象时,它们才相同。
let a = []; // 变量 a 指向一个空数组。 let b = a; // 现在 b 指向同一个数组。 b[0] = 1; // 改变变量 b 引用的数组。 a[0] // => 1: 更改也通过变量 a 可见。 a === b // => true: a 和 b 指向同一个对象,所以它们相等。
从这段代码中可以看出,将对象(或数组)赋给变量只是赋予了引用:它并不创建对象的新副本。如果要创建对象或数组的新副本,必须显式复制对象的属性或数组的元素。这个示例演示了使用for
循环(§5.4.3):
let a = ["a","b","c"]; // 我们想要复制的数组 let b = []; // 我们将复制到的不同数组 for(let i = 0; i < a.length; i++) { // 对于 a[]的每个索引 b[i] = a[i]; // 将 a 的一个元素复制到 b } let c = Array.from(b); // 在 ES6 中,使用 Array.from()复制数组
同样,如果我们想比较两个不同的对象或数组,我们必须比较它们的属性或元素。以下代码定义了一个比较两个数组的函数:
function equalArrays(a, b) { if (a === b) return true; // 相同的数组是相等的 if (a.length !== b.length) return false; // 不同大小的数组不相等 for(let i = 0; i < a.length; i++) { // 遍历所有元素 if (a[i] !== b[i]) return false; // 如果有任何不同,数组不相等 } return true; // 否则它们是相等的 }
JavaScript 权威指南第七版(GPT 重译)(一)(4)https://developer.aliyun.com/article/1485289