JavaScript 权威指南第七版(GPT 重译)(二)(1)https://developer.aliyun.com/article/1485292
4.9.2 比较运算符
这些比较运算符测试它们的两个操作数的相对顺序(数字或字母):
小于 (<
)
<
运算符在其第一个操作数小于第二个操作数时求值为true
;否则,求值为false
。
大于 (>
)
>
运算符在其第一个操作数大于第二个操作数时求值为true
;否则,求值为false
。
小于或等于 (<=
)
<=
运算符在其第一个操作数小于或等于第二个操作数时求值为true
;否则,求值为false
。
大于或等于 (>=
)
>=
运算符在其第一个操作数大于或等于第二个操作数时求值为true
;否则,求值为false
。
这些比较运算符的操作数可以是任何类型。但是,比较只能在数字和字符串上执行,因此不是数字或字符串的操作数将被转换。
比较和转换如下进行:
- 如果任一操作数评估为对象,则将该对象转换为原始值,如§3.9.3 末尾所述;如果其
valueOf()
方法返回原始值,则使用该值。否则,使用其toString()
方法的返回值。 - 如果在任何必要的对象到原始值转换后,两个操作数都是字符串,则比较这两个字符串,使用字母顺序,其中“字母顺序”由组成字符串的 16 位 Unicode 值的数值顺序定义。
- 如果在对象到原始值转换后,至少有一个操作数不是字符串,则两个操作数都将转换为数字并进行数值比较。
0
和-0
被视为相等。Infinity
大于除自身以外的任何数字,而-Infinity
小于除自身以外的任何数字。如果任一操作数是(或转换为)NaN
,则比较运算符始终返回false
。尽管算术运算符不允许 BigInt 值与常规数字混合使用,但比较运算符允许数字和 BigInt 之间的比较。
请记住,JavaScript 字符串是 16 位整数值的序列,并且字符串比较只是对两个字符串中的值进行数值比较。Unicode 定义的数值编码顺序可能与任何特定语言或区域设置中使用的传统排序顺序不匹配。特别注意,字符串比较区分大小写,所有大写 ASCII 字母都“小于”所有小写 ASCII 字母。如果您没有预期,此规则可能导致令人困惑的结果。例如,根据<
运算符,字符串“Zoo”在字符串“aardvark”之前。
对于更强大的字符串比较算法,请尝试String.localeCompare()
方法,该方法还考虑了特定区域设置的字母顺序定义。对于不区分大小写的比较,您可以使用String.toLowerCase()
或String.toUpperCase()
将字符串转换为全小写或全大写。而且,为了使用更通用且更好本地化的字符串比较工具,请使用§11.7.3 中描述的 Intl.Collator 类。
+
运算符和比较运算符对数字和字符串操作数的行为不同。+
偏向于字符串:如果任一操作数是字符串,则执行连接操作。比较运算符偏向于数字,只有在两个操作数都是字符串时才执行字符串比较:
1 + 2 // => 3: addition. "1" + "2" // => "12": concatenation. "1" + 2 // => "12": 2 is converted to "2". 11 < 3 // => false: numeric comparison. "11" < "3" // => true: string comparison. "11" < 3 // => false: numeric comparison, "11" converted to 11. "one" < 3 // => false: numeric comparison, "one" converted to NaN.
最后,请注意<=
(小于或等于)和>=
(大于或等于)运算符不依赖于相等或严格相等运算符来确定两个值是否“相等”。相反,小于或等于运算符简单地定义为“不大于”,大于或等于运算符定义为“不小于”。唯一的例外是当任一操作数是(或转换为)NaN
时,此时所有四个比较运算符都返回false
。
4.9.3 in 运算符
in
运算符期望左侧操作数是一个字符串、符号或可转换为字符串的值。它期望右侧操作数是一个对象。如果左侧值是右侧对象的属性名称,则评估为true
。例如:
let point = {x: 1, y: 1}; // Define an object "x" in point // => true: object has property named "x" "z" in point // => false: object has no "z" property. "toString" in point // => true: object inherits toString method let data = [7,8,9]; // An array with elements (indices) 0, 1, and 2 "0" in data // => true: array has an element "0" 1 in data // => true: numbers are converted to strings 3 in data // => false: no element 3
4.9.4 instanceof 运算符
instanceof
运算符期望左侧操作数是一个对象,右侧操作数标识对象类。如果左侧对象是右侧类的实例,则运算符评估为true
,否则评估为false
。第九章解释了在 JavaScript 中,对象类由初始化它们的构造函数定义。因此,instanceof
的右侧操作数应该是一个函数。以下是示例:
let d = new Date(); // Create a new object with the Date() constructor d instanceof Date // => true: d was created with Date() d instanceof Object // => true: all objects are instances of Object d instanceof Number // => false: d is not a Number object let a = [1, 2, 3]; // Create an array with array literal syntax a instanceof Array // => true: a is an array a instanceof Object // => true: all arrays are objects a instanceof RegExp // => false: arrays are not regular expressions
注意所有对象都是Object
的实例。instanceof
在判断一个对象是否是某个类的实例时会考虑“超类”。如果instanceof
的左操作数不是对象,则返回false
。如果右操作数不是对象类,则抛出TypeError
。
要理解instanceof
运算符的工作原理,您必须了解“原型链”。这是 JavaScript 的继承机制,描述在§6.3.2 中。要评估表达式o instanceof f
,JavaScript 会评估f.prototype
,然后在o
的原型链中查找该值。如果找到,则o
是f
的实例(或f
的子类),运算符返回true
。如果f.prototype
不是o
的原型链中的值之一,则o
不是f
的实例,instanceof
返回false
。
4.10 逻辑表达式
逻辑运算符&&
、||
和!
执行布尔代数,通常与关系运算符结合使用,将两个关系表达式组合成一个更复杂的表达式。这些运算符在接下来的小节中描述。为了完全理解它们,您可能需要回顾§3.4 中介绍的“真值”和“假值”概念。
4.10.1 逻辑 AND(&&)
&&
运算符可以在三个不同级别理解。在最简单的级别上,当与布尔操作数一起使用时,&&
对这两个值执行布尔 AND 操作:仅当其第一个操作数和第二个操作数都为true
时才返回true
。如果其中一个或两个操作数为false
,则返回false
。
&&
经常用作连接两个关系表达式的连接词:
x === 0 && y === 0 // true if, and only if, x and y are both 0
关系表达式始终评估为true
或false
,因此在这种情况下,&&
运算符本身返回true
或false
。关系运算符的优先级高于&&
(和||
),因此可以安全地写出不带括号的表达式。
但是&&
不要求其操作数是布尔值。回想一下,所有 JavaScript 值都是“真值”或“假值”。(有关详细信息,请参阅§3.4。假值包括false
、null
、undefined
、0
、-0
、NaN
和""
。所有其他值,包括所有对象,都是真值。)&&
的第二个级别可以理解为真值和假值的布尔 AND 运算符。如果两个操作数都是真值,则运算符返回真值。否则,一个或两个操作数必须是假值,运算符返回假值。在 JavaScript 中,任何期望布尔值的表达式或语句都可以使用真值或假值,因此&&
并不总是返回true
或false
不会造成实际问题。
请注意,此描述指出该运算符返回“真值”或“假值”,但没有指定该值是什么。为此,我们需要在第三个最终级别描述&&
。该运算符首先评估其第一个操作数,即左侧的表达式。如果左侧的值为假,整个表达式的值也必须为假,因此&&
只返回左侧的值,甚至不评估右侧的表达式。
另一方面,如果左侧的值为真值,则表达式的整体值取决于右侧的值。如果右侧的值为真值,则整体值必须为真值,如果右侧的值为假值,则整体值必须为假值。因此,当左侧的值为真值时,&&
运算符评估并返回右侧的值:
let o = {x: 1}; let p = null; o && o.x // => 1: o is truthy, so return value of o.x p && p.x // => null: p is falsy, so return it and don't evaluate p.x
重要的是要理解 &&
可能会或可能不会评估其右侧操作数。在这个代码示例中,变量 p
被设置为 null
,并且表达式 p.x
如果被评估,将导致 TypeError。但是代码以一种惯用的方式使用 &&
,以便仅在 p
为真值时才评估 p.x
,而不是 null
或 undefined
。
&&
的行为有时被称为短路,你可能会看到故意利用这种行为有条件地执行代码的代码。例如,下面两行 JavaScript 代码具有等效的效果:
if (a === b) stop(); // Invoke stop() only if a === b (a === b) && stop(); // This does the same thing
一般来说,当你在 &&
的右侧写一个具有副作用(赋值、递增、递减或函数调用)的表达式时,你必须小心。这些副作用是否发生取决于左侧的值。
尽管这个运算符实际上的工作方式有些复杂,但它最常用作一个简单的布尔代数运算符,适用于真值和假值。
4.10.2 逻辑 OR (||)
||
运算符对其两个操作数执行布尔 OR 操作。如果一个或两个操作数为真值,则返回真值。如果两个操作数都为假值,则返回假值。
尽管 ||
运算符通常被简单地用作布尔 OR 运算符,但它和 &&
运算符一样,具有更复杂的行为。它首先评估其第一个操作数,即左侧的表达式。如果这个第一个操作数的值为真值,它会短路并返回该真值,而不会评估右侧的表达式。另一方面,如果第一个操作数的值为假值,则 ||
评估其第二个操作数并返回该表达式的值。
与 &&
运算符一样,你应该避免包含副作用的右侧操作数,除非你故意想要利用右侧表达式可能不会被评估的事实。
这个运算符的一个惯用用法是在一组备选项中选择第一个真值:
// If maxWidth is truthy, use that. Otherwise, look for a value in // the preferences object. If that is not truthy, use a hardcoded constant. let max = maxWidth || preferences.maxWidth || 500;
请注意,如果 0 是 maxWidth
的合法值,则此代码将无法正常工作,因为 0 是一个假值。参见 ??
运算符(§4.13.2)以获取替代方案。
在 ES6 之前,这种习惯通常用于函数中为参数提供默认值:
// Copy the properties of o to p, and return p function copy(o, p) { p = p || {}; // If no object passed for p, use a newly created object. // function body goes here }
然而,在 ES6 及以后,这个技巧不再需要,因为默认参数值可以直接写在函数定义中:function copy(o, p={}) { ... }
。
4.10.3 逻辑 NOT (!)
!
运算符是一个一元运算符;它放在单个操作数之前。它的目的是反转其操作数的布尔值。例如,如果 x
是真值,!x
评估为 false
。如果 x
是假值,则 !x
是 true
。
与 &&
和 ||
运算符不同,!
运算符在反转转换其操作数为布尔值(使用 第三章 中描述的规则)之前。这意味着 !
总是返回 true
或 false
,你可以通过两次应用这个运算符将任何值 x
转换为其等效的布尔值:!!x
(参见 §3.9.2)。
作为一元运算符,!
具有高优先级并且紧密绑定。如果你想反转类似 p && q
的表达式的值,你需要使用括号:!(p && q)
。值得注意的是,我们可以使用 JavaScript 语法表达布尔代数的两个定律:
// DeMorgan's Laws !(p && q) === (!p || !q) // => true: for all values of p and q !(p || q) === (!p && !q) // => true: for all values of p and q
4.11 赋值表达式
JavaScript 使用 =
运算符将一个值分配给一个变量或属性。例如:
i = 0; // Set the variable i to 0. o.x = 1; // Set the property x of object o to 1.
=
运算符期望其左侧操作数是一个 lvalue:一个变量或对象属性(或数组元素)。它期望其右侧操作数是任何类型的任意值。赋值表达式的值是右侧操作数的值。作为副作用,=
运算符将右侧的值分配给左侧的变量或属性,以便将来对变量或属性的引用评估为该值。
虽然赋值表达式通常相当简单,但有时您可能会看到赋值表达式的值作为更大表达式的一部分使用。例如,您可以使用以下代码在同一表达式中赋值和测试一个值:
(a = b) === 0
如果这样做,请确保您清楚=
和===
运算符之间的区别!请注意,=
的优先级非常低,当赋值的值要在更大的表达式中使用时,通常需要括号。
赋值运算符具有从右到左的结合性,这意味着当表达式中出现多个赋值运算符时,它们将从右到左进行评估。因此,您可以编写如下代码将单个值分配给多个变量:
i = j = k = 0; // Initialize 3 variables to 0
4.11.1 带操作符的赋值
除了正常的=
赋值运算符外,JavaScript 还支持许多其他赋值运算符,通过将赋值与其他操作结合起来提供快捷方式。例如,+=
运算符执行加法和赋值。以下表达式:
total += salesTax;
等同于这个:
total = total + salesTax;
正如您所期望的那样,+=
运算符适用于数字或字符串。对于数字操作数,它执行加法和赋值;对于字符串操作数,它执行连接和赋值。
类似的运算符包括-=
、*=
、&=
等。表 4-2 列出了它们全部。
表 4-2. 赋值运算符
运算符 | 示例 | 等价 |
+= |
a += b |
a = a + b |
-= |
a -= b |
a = a - b |
*= |
a *= b |
a = a * b |
/= |
a /= b |
a = a / b |
%= |
a %= b |
a = a % b |
**= |
a **= b |
a = a ** b |
<<= |
a <<= b |
a = a << b |
>>= |
a >>= b |
a = a >> b |
>>>= |
a >>>= b |
a = a >>> b |
&= |
a &= b |
a = a & b |
|= |
a |= b |
a = a | b |
^= |
a ^= b |
a = a ^ b |
在大多数情况下,表达式:
a op= b
其中op
是一个运算符,等价于表达式:
a = a op b
在第一行中,表达式a
被评估一次。在第二行中,它被评估两次。这两种情况只有在a
包含函数调用或增量运算符等副作用时才会有所不同。例如,以下两个赋值是不同的:
data[i++] *= 2; data[i++] = data[i++] * 2;
4.12 评估表达式
与许多解释性语言一样,JavaScript 有解释 JavaScript 源代码字符串并对其进行评估以生成值的能力。JavaScript 使用全局函数eval()
来实现这一点:
eval("3+2") // => 5
动态评估源代码字符串是一种强大的语言特性,在实践中几乎从不需要。如果您发现自己使用eval()
,您应该仔细考虑是否真的需要使用它。特别是,eval()
可能存在安全漏洞,您绝不应将任何源自用户输入的字符串传递给eval()
。由于 JavaScript 这样复杂的语言,没有办法对用户输入进行清理以使其安全用于eval()
。由于这些安全问题,一些 Web 服务器使用 HTTP 的“内容安全策略”头部来禁用整个网站的eval()
。
接下来的小节将解释eval()
的基本用法,并解释两个对优化器影响较小的受限版本。
4.12.1 eval()
eval()
期望一个参数。如果传递的值不是字符串,则它只是返回该值。如果传递一个字符串,则它尝试将字符串解析为 JavaScript 代码,如果失败则抛出 SyntaxError。如果成功解析字符串,则评估代码并返回字符串中最后一个表达式或语句的值,如果最后一个表达式或语句没有值,则返回undefined
。如果评估的字符串引发异常,则该异常从调用eval()
传播出来。
eval()
的关键之处(在这种情况下调用)是它使用调用它的代码的变量环境。也就是说,它查找变量的值,并以与局部代码相同的方式定义新变量和函数。如果一个函数定义了一个局部变量x
,然后调用eval("x")
,它将获得局部变量的值。如果它调用eval("x=1")
,它会改变局部变量的值。如果函数调用eval("var y = 3;")
,它会声明一个新的局部变量y
。另一方面,如果被评估的字符串使用let
或const
,则声明的变量或常量将局部于评估,并不会在调用环境中定义。
类似地,函数可以使用以下代码声明一个局部函数:
eval("function f() { return x+1; }");
如果你从顶层代码调用eval()
,它当然会操作全局变量和全局函数。
请注意,传递给eval()
的代码字符串必须在语法上是合理的:你不能使用它来将代码片段粘贴到函数中。例如,写eval("return;")
是没有意义的,因为return
只在函数内部合法,而被评估的字符串使用与调用函数相同的变量环境并不使其成为该函数的一部分。如果你的字符串作为独立脚本是合理的(即使是非常简短的像x=0
),那么它是可以传递给eval()
的。否则,eval()
会抛出 SyntaxError。
4.12.2 全局 eval()
正是eval()
改变局部变量的能力让 JavaScript 优化器感到困扰。然而,作为一种解决方法,解释器只是对调用eval()
的任何函数进行较少的优化。但是,如果一个脚本定义了eval()
的别名,然后通过另一个名称调用该函数,JavaScript 规范声明,当eval()
被任何名称调用时,除了“eval”之外,它应该评估字符串,就像它是顶层全局代码一样。被评估的代码可以定义新的全局变量或全局函数,并且可以设置全局变量,但不会使用或修改调用函数的局部变量,因此不会干扰局部优化。
“直接 eval”是使用确切的、未限定名称“eval”调用eval()
函数的表达式(开始感觉像是一个保留字)。直接调用eval()
使用调用上下文的变量环境。任何其他调用——间接调用——使用全局对象作为其变量环境,不能读取、写入或定义局部变量或函数。(直接和间接调用只能使用var
定义新变量。在评估的字符串中使用let
和const
会创建仅在评估中局部的变量和常量,不会改变调用或全局环境。)
以下代码演示:
const geval = eval; // Using another name does a global eval let x = "global", y = "global"; // Two global variables function f() { // This function does a local eval let x = "local"; // Define a local variable eval("x += 'changed';"); // Direct eval sets local variable return x; // Return changed local variable } function g() { // This function does a global eval let y = "local"; // A local variable geval("y += 'changed';"); // Indirect eval sets global variable return y; // Return unchanged local variable } console.log(f(), x); // Local variable changed: prints "localchanged global": console.log(g(), y); // Global variable changed: prints "local globalchanged":
请注意,进行全局 eval 的能力不仅仅是为了优化器的需要;实际上,这是一个非常有用的功能,允许你执行字符串代码,就像它们是独立的顶层脚本一样。正如本节开头所述,真正需要评估代码字符串是罕见的。但是如果你确实发现有必要,你更可能想要进行全局 eval 而不是局部 eval。
4.12.3 严格 eval()
严格模式(参见§5.6.3)对eval()
函数的行为甚至对标识符“eval”的使用施加了进一步的限制。当从严格模式代码中调用eval()
,或者当要评估的代码字符串本身以“use strict”指令开头时,eval()
会使用私有变量环境进行局部评估。这意味着在严格模式下,被评估的代码可以查询和设置局部变量,但不能在局部范围内定义新变量或函数。
此外,严格模式使 eval()
更像是一个运算符,有效地将“eval”变成了一个保留字。你不能用新值覆盖 eval()
函数。你也不能声明一个名为“eval”的变量、函数、函数参数或 catch 块参数。
4.13 其他运算符
JavaScript 支持许多其他杂项运算符,详细描述在以下章节。
4.13.1 条件运算符 (?😃
条件运算符是 JavaScript 中唯一的三元运算符,有时实际上被称为三元运算符。这个运算符有时被写为 ?:
,尽管在代码中看起来并不完全是这样。因为这个运算符有三个操作数,第一个在 ?
前面,第二个在 ?
和 :
之间,第三个在 :
后面。使用方法如下:
x > 0 ? x : -x // The absolute value of x
条件运算符的操作数可以是任何类型。第一个操作数被评估并解释为布尔值。如果第一个操作数的值为真值,则评估第二个操作数,并返回其值。否则,如果第一个操作数为假值,则评估第三个操作数,并返回其值。第二个和第三个操作数中只有一个被评估;永远不会同时评估两个。
虽然可以使用 if
语句 (§5.3.1) 实现类似的结果,但 ?:
运算符通常提供了一个便捷的快捷方式。以下是一个典型的用法,检查变量是否已定义(并具有有意义的真值),如果是,则使用它,否则提供默认值:
greeting = "hello " + (username ? username : "there");
这等同于以下 if
语句,但更简洁:
greeting = "hello "; if (username) { greeting += username; } else { greeting += "there"; }
4.13.2 第一个定义的 (??)
第一个定义运算符 ??
的值为其第一个定义的操作数:如果其左操作数不是 null
且不是 undefined
,则返回该值。否则,返回右操作数的值。与 &&
和 ||
运算符一样,??
是短路运算:只有在第一个操作数评估为 null
或 undefined
时才评估第二个操作数。如果表达式 a
没有副作用,那么表达式 a ?? b
等效于:
(a !== null && a !== undefined) ? a : b
当你想选择第一个定义的操作数而不是第一个真值操作数时,??
是 ||
(§4.10.2) 的一个有用替代。虽然 ||
名义上是一个逻辑 OR 运算符,但它也被习惯性地用来选择第一个非假值操作数,例如以下代码:
// If maxWidth is truthy, use that. Otherwise, look for a value in // the preferences object. If that is not truthy, use a hardcoded constant. let max = maxWidth || preferences.maxWidth || 500;
这种习惯用法的问题在于零、空字符串和 false
都是假值,在某些情况下可能是完全有效的值。在这个代码示例中,如果 maxWidth
是零,则该值将被忽略。但如果我们将 ||
运算符改为 ??
,我们最终得到一个零是有效值的表达式:
// If maxWidth is defined, use that. Otherwise, look for a value in // the preferences object. If that is not defined, use a hardcoded constant. let max = maxWidth ?? preferences.maxWidth ?? 500;
以下是更多示例,展示了当第一个操作数为假值时 ??
的工作原理。如果该操作数为假值但已定义,则 ??
返回它。只有当第一个操作数为“nullish”(即 null
或 undefined
)时,该运算符才会评估并返回第二个操作数:
let options = { timeout: 0, title: "", verbose: false, n: null }; options.timeout ?? 1000 // => 0: as defined in the object options.title ?? "Untitled" // => "": as defined in the object options.verbose ?? true // => false: as defined in the object options.quiet ?? false // => false: property is not defined options.n ?? 10 // => 10: property is null
请注意,如果我们使用 ||
而不是 ??
,这里的 timeout
、title
和 verbose
表达式将具有不同的值。
??
运算符类似于 &&
和 ||
运算符,但它的优先级既不高于它们,也不低于它们。如果你在一个表达式中使用它与这些运算符之一,你必须使用显式括号来指定你想要先执行哪个操作:
(a ?? b) || c // ?? first, then || a ?? (b || c) // || first, then ?? a ?? b || c // SyntaxError: parentheses are required
??
运算符由 ES2020 定义,在 2020 年初,所有主要浏览器的当前版本或 beta 版本都新支持该运算符。这个运算符正式称为“nullish coalescing”运算符,但我避免使用这个术语,因为这个运算符选择其操作数之一,但在我看来并没有以任何方式“合并”它们。
4.13.3 typeof 运算符
typeof
是一个一元运算符,放置在其单个操作数之前,该操作数可以是任何类型。它的值是一个指定操作数类型的字符串。Table 4-3 指定了typeof
运算符对任何 JavaScript 值的值。
表 4-3。typeof 运算符返回的值
x |
typeof x |
undefined |
"undefined" |
null |
"object" |
true 或 false |
"boolean" |
任何数字或 NaN |
"number" |
任何 BigInt | "bigint" |
任何字符串 | "string" |
任何符号 | "symbol" |
任何函数 | "function" |
任何非函数对象 | "object" |
您可能会在表达式中使用typeof
运算符,如下所示:
// If the value is a string, wrap it in quotes, otherwise, convert (typeof value === "string") ? "'" + value + "'" : value.toString()
注意,如果操作数值为null
,typeof
返回“object”。如果要区分null
和对象,您必须明确测试这种特殊情况的值。
尽管 JavaScript 函数是一种对象,但typeof
运算符认为函数与其他对象有足够大的不同,因此它们有自己的返回值。
因为对于除函数之外的所有对象和数组值,typeof
都会评估为“object”,所以它只有在区分对象和其他原始类型时才有用。为了区分一个类的对象与另一个类的对象,您必须使用其他技术,如instanceof
运算符(参见§4.9.4)、class
属性(参见§14.4.3)或constructor
属性(参见§9.2.2 和§14.3)。
4.13.4 delete 运算符
delete
是一个一元运算符,试图删除指定为其操作数的对象属性或数组元素。与赋值、递增和递减运算符一样,delete
通常用于其属性删除副作用,而不是用于其返回的值。一些例子:
let o = { x: 1, y: 2}; // Start with an object delete o.x; // Delete one of its properties "x" in o // => false: the property does not exist anymore let a = [1,2,3]; // Start with an array delete a[2]; // Delete the last element of the array 2 in a // => false: array element 2 doesn't exist anymore a.length // => 3: note that array length doesn't change, though
请注意,删除的属性或数组元素不仅仅被设置为undefined
值。当删除属性时,该属性将不再存在。尝试读取不存在的属性会返回undefined
,但您可以使用in
运算符(§4.9.3)测试属性的实际存在性。删除数组元素会在数组中留下一个“空洞”,并且不会更改数组的长度。结果数组是稀疏的(§7.3)。
delete
期望其操作数为左值。如果它不是左值,则运算符不起作用并返回true
。否则,delete
会尝试删除指定的左值。如果成功删除指定的左值,则delete
返回true
。然而,并非所有属性都可以被删除:不可配置的属性(§14.1)不受删除的影响。
在严格模式下,如果其操作数是未经限定的标识符,如变量、函数或函数参数,则delete
会引发 SyntaxError:它仅在操作数为属性访问表达式时起作用(§4.4)。严格模式还指定,如果要删除任何不可配置的(即不可删除的)属性,则delete
会引发 TypeError。在严格模式之外,这些情况不会发生异常,delete
简单地返回false
,表示无法删除操作数。
以下是delete
运算符的一些示例用法:
let o = {x: 1, y: 2}; delete o.x; // Delete one of the object properties; returns true. typeof o.x; // Property does not exist; returns "undefined". delete o.x; // Delete a nonexistent property; returns true. delete 1; // This makes no sense, but it just returns true. // Can't delete a variable; returns false, or SyntaxError in strict mode. delete o; // Undeletable property: returns false, or TypeError in strict mode. delete Object.prototype;
我们将在§6.4 中再次看到delete
运算符。
4.13.5 await 运算符
await
在 ES2017 中引入,作为使 JavaScript 中的异步编程更自然的一种方式。您需要阅读第十三章以了解此运算符。简而言之,await
期望一个 Promise 对象(表示异步计算)作为其唯一操作数,并使您的程序表现得好像正在等待异步计算完成(但实际上不会阻塞,并且不会阻止其他异步操作同时进行)。await
运算符的值是 Promise 对象的完成值。重要的是,await
只在使用async
关键字声明的函数内部合法。再次查看第十三章获取完整详情。
4.13.6 void 运算符
void
是一个一元运算符,出现在其单个操作数之前,该操作数可以是任何类型。这个运算符是不寻常且很少使用的;它评估其操作数,然后丢弃值并返回undefined
。由于操作数值被丢弃,只有在操作数具有副作用时使用void
运算符才有意义。
void
运算符如此隐晦,以至于很难想出其使用的实际示例。一个情况是当您想要定义一个什么都不返回但也使用箭头函数快捷语法的函数时(参见§8.1.3),其中函数体是一个被评估并返回的单个表达式。如果您仅仅为了其副作用而评估表达式,并且不想返回其值,那么最简单的方法是在函数体周围使用大括号。但是,作为替代方案,在这种情况下您也可以使用void
运算符:
let counter = 0; const increment = () => void counter++; increment() // => undefined counter // => 1
4.13.7 逗号运算符(,)
逗号运算符是一个二元运算符,其操作数可以是任何类型。它评估其左操作数,评估其右操作数,然后返回右操作数的值。因此,以下行:
i=0, j=1, k=2;
评估为 2,基本上等同于:
i = 0; j = 1; k = 2;
左侧表达式始终被评估,但其值被丢弃,这意味着只有在左侧表达式具有副作用时才有意义使用逗号运算符。逗号运算符通常使用的唯一情况是在具有多个循环变量的for
循环(§5.4.3)中:
// The first comma below is part of the syntax of the let statement // The second comma is the comma operator: it lets us squeeze 2 // expressions (i++ and j--) into a statement (the for loop) that expects 1. for(let i=0,j=10; i < j; i++,j--) { console.log(i+j); }
JavaScript 权威指南第七版(GPT 重译)(二)(3)https://developer.aliyun.com/article/1485294