JavaScript语言基础之操作符
本文最后更新于 2025年7月30日 下午
参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators 。
ECMA-262 描述了一组可用于操作数据值的操作符,包括数学操作符(如加、减)、位操作符、关系操作符和相等操作符等。ECMAScript 中的操作符是独特的,因为它们可用于各种值,包括字符串、数值、布尔值,甚至还有对象。在应用给对象时,操作符通常会调用 valueOf()
或 toString()
方法来取得可以计算的值。
概述
注:
- 期待操作符类型中的
lval
即lvalue(左值)
,意思是“一个可以合法地出现在赋值表达式左侧的表达式”。在 JavaScript 中,变量、对象元素、数组元素都是“左值”。 - 下表的操作符是按照优先级从高到低的顺序排列的,其中的横线分组了相同优先级的操作符。
- “左”表示结合性为从左到右,“右”表示结合性为从右到左。
操作数个数
操作符可以按照它们期待的操作数个数(参数数量)来分类。
多数的 JavaScript 操作符都是二元操作符,可以将两个表达式组合成一个更复杂的表达式。换句话说,这些操作符期待两个操作数。
同时,JavaScript 也支持一元操作符,这些操作符将一个表达式转换为另一个更复杂的表达式。
最后,JavaScript 也支持一个三元操作符,即条件操作符,用于将三个表达式组合为一个表达式。
操作数与结果类型
有些操作数用于任何类型的数值,但是多数操作符期待自己的操作数是某种特定类型,而且多数操作符也返回(或求值为)特定类型的值,具体如上表。
JavaScript 操作符通常按照需要转换操作数的类型。如乘法操作符期待数值参数,但表达式'3' * '5'
之所以合法,是因为 JavaScript 可以把操作数转化为数值,且其结果是数值 15 而不是字符串 15。
也要记住,每个 JavaScript 值要么是“真值”要么是“假值”,因此期待布尔值的操作符可以用于任何类型的操作符。
有些操作符的行为会因为操作数类型的不同而不同。最明显的就是加法操作符,其可以把数值加起来,也可以拼接字符串;还有比较操作符根据操作数类型会俺早数值顺序或字母表顺序比较。
操作符副效应
大部分如2 * 3
这样的简单表达式求值不会影响程序状态,程序后续的计算也不会北这个求值所影响。
但是有些表达式是有副效应的,即对它们求值可能影响将来求值的结果。赋值操作符就是一例,把一个值赋给变量或属性,会改变后续使用该变量或属性的表达式的值。同理,递增和递减操作符也有副效应。此外,delete
操作符的副效应在于删除属性类似于(但不同于)给属性赋值undefined
。
其它的操作符都没有副效应,但函数调用和对象创建表达式是否有副效应,取决于函数和构造函数体内是否使用了有副效应的操作符。
操作符优先级
靠近表格顶部的操作符咸鱼靠近表格底部的操作符先执行。
如 w = x + y * z
,其中,乘法计算先与加法执行,最后赋值给左侧的变量。不过,具体的执行顺序可以由圆括号改写:``w = (x + y) * z`,这样便会先执行加法后进行乘法计算。
但是要注意,属性访问和调用表达式的优先级高于表格中的任何操作符。因为操作符要基于属性访问、数组索引和函数调用的结果执行,所以这些操作的优先级全部高于操作符。
如果不确定优先级,最简单的方法是使用圆括号明确求值顺序。最重要的规则在于:乘和除先于加和减执行,而赋值优先级很低,几乎总是最后执行。
JavaScript 新增的操作符并不总是符合这个优先级模式。比如,??
操作符比 ||
和 &&
优先级低,而实际上它相对于这两个操作符的优先级并没有定义,ES2020 要求在混用 ??
和 ||
或 &&
时必须使用圆括号。类似地,**
相对于一元负值操作符的优先级也没有定义,因此在同时求负值和求幂时也必须使用圆括号。
操作符结合性
操作符结合性规定了相同优先级操作的执行顺序。左结合性意味着操作操作从做到右执行。
1 |
|
这是因为,减操作符具有左结合性;而幂、一元、赋值、三元操作符具有右结合性。
求值顺序
操作符的优先级和结合性规定了复杂表达式中操作的执行顺序,但它们没有规定子表达式的求值顺序。JavaScript 始终严格按照从左到右的顺序对表达式求值。
例如,在表达式 w = x + y * z
中,子表达式 w 首先被求值,再对 x、y 和 z 求值。然后将 y 和 z 相乘,加到 x 上,再把结果赋值给表达式 w 表示的变量或属性。使用圆括号会改变执行顺序,但不会改变从左到右的求值顺序。
一元操作符
只操作一个值的操作符叫一元操作符,是 ECMAScript 中最简单的操作符。
递增和递减操作符
递增和递减操作符直接照搬自 C 语言,但有两个版本:前缀版和后缀版。顾名思义,前缀版就是位于要操作的变量前头,后缀版就是位于要操作的变量后头。
- 前缀版:在跟其他操作混合时,变量的值在语句被求值之前改变(先递增递减后运算语句)
- 后缀版:在跟其他操作混合时,变量的值在语句被求值之后发生(先运算语句后递增递减)
1 |
|
这 4 个操作符可作用于任何值,整数、字符串、布尔值、浮点值,甚至对象都可。递增和递减操作符遵循如下规则:
- 数值,直接改变后返回
- 布尔值,
true
转换为 1,false
转换为 0,再应用改变,变量类型从布尔值变成数值 - 字符串,如果是有效的数值形式,则转换为数值再应用改变;如果非有效的数值形式,则将变量的值设置为 NaN ,变量类型从字符串变成数值
- 对象,则调用其
valueOf()
方法取得可以操作的值。对得到的值应用上述规则。如果是NaN
,则调用toString()
并再次应用其他规则
一元加和减操作符
它们在 ECMAScript 中跟在高中数学中的用途一样。
一元加由一个加号(+
)表示,放在变量前头,对数值没有任何影响。如果将一元加应用到非数值,则会执行与使用 Number()
转型函数一样的类型转换:布尔值 false
和 true
转换为 0 和 1,字符串根据特殊规则进行解析,对象会调用它们的 valueOf()
或 toString()
方法以得到可以转换的值。
一元减由一个减号(-
)表示,放在变量前头,用于把数值变成负值。如果将一元减应用到非数值,一元减会遵循与一元加同样的规则,先进行转换然后取负值。
位操作符
下面介绍的操作符用于数值的底层操作,也就是操作内存中表示数据的比特(位)。
布尔操作符
对于编程语言来说,布尔操作符跟相等操作符同样重要。如果没有能力测试两个值的关系,那么像 if-else
和循环这样的语句也没什么用。布尔操作符一共有 3 个:逻辑非(!
)、逻辑与(&&
)和逻辑或(||
)。
逻辑非(!
)
可应用给 ECMAScript 中的任何值。这个操作符始终返回布尔值,无论应用到的是什么数据类型。逻辑非操作符首先将操作数转换为布尔值,然后再对其取反,遵循如下规则:
- 如果操作数是对象,则返回
false
- 如果操作数是符号,则返回
false
- 如果操作数是空字符串,则返回
true
- 如果操作数是非空字符串,则返回
false
- 如果操作数是数值 0,则返回
true
- 如果操作数是非 0 数值(包括 Infinity),则返回
false
- 如果操作数是
null
,则返回true
- 如果操作数是
NaN
,则返回true
- 如果操作数是
undefined
,则返回true
逻辑非操作符也可以用于把任意值转换为布尔值。同时使用两个叹号(!!
),相当于调用转型函数 Boolean()
。无论操作数是什么类型,第一个叹号总会返回布尔值。第二个叹号对该布尔值取反,从而给出变量真正对应的布尔值。结果与使用 Boolean()
函数一样。
逻辑与(&&
)
逻辑与操作符可用于任何类型的操作数。如果有操作数不是布尔值,则逻辑与并不一定会返回布尔值,而是遵循如下规则:
- 如果有一个操作数是
null
,则返回null
- 如果有一个操作数是
NaN
,则返回NaN
- 如果有一个操作数是
undefined
,则返回undefined
- 如果第一个操作数是对象,则返回第二个操作数
- 如果第二个操作数是对象,则只第一个操作数求值为
true
才会返回该对象 - 如果两个操作数都是对象,则返回第二个操作数
逻辑与操作符是一种短路操作符,意思就是如果第一个操作数决定了结果,那么永远不会对第二个操作数求值。对逻辑与操作符来说,如果第一个操作数是 false
,那么无论第二个操作数是什么值,结果也不可能等于 true
。
逻辑或(||
)
与逻辑与类似,如果有一个操作数不是布尔值,那么逻辑或操作符也不一定返回布尔值。它遵循如下规则
- 如果两个操作数都是
null
,则返回null
- 如果两个操作数都是
NaN
,则返回NaN
- 如果两个操作数都是
undefined
,则返回undefined
- 如果第一个操作数求值为
false
,则返回第二个操作数 - 如果第一个操作数是对象,则返回第一个操作数
- 如果两个操作数都是对象,则返回第一个操作数
同样与逻辑与类似,逻辑或操作符也具有短路的特性。第一个操作数求值为 true
,第二个操作数就不会再被求值。
乘性操作符
ECMAScript 定义了 3 个乘性操作符:乘法、除法和取模。这些操作符跟它们在 Java、C 语言及 Perl 中对应的操作符作用一样,但在处理非数值时,它们也会包含一些自动的类型转换。如果乘性操作符有不是数值的操作数,则该操作数会在后台被使用 Number()
转型函数转换为数值。
乘法操作符(*
)
用于计算两个数值的乘积。乘法操作符在处理特殊值时也有一些特殊的行为:
- 若操作数都是数值,则执行常规的乘法运算,即两个正值相乘是正值,两个负值相乘也是正值,正负符号不同的值相乘得到负值。如果 ECMAScript 不能表示乘积,则返回 Infinity 或-Infinity
- 若有任一操作数是
NaN
,则返回NaN
- 若是 Infinity 乘以 0,则返回
NaN
- 若是 Infinity 乘以非 0 的有限数值,则根据第二个操作数的符号返回 Infinity 或 -Infinity
- 若是 Infinity 乘以 Infinity,则返回 Infinity
- 若有不是数值的操作数,则先用
Number()
将其转换为数值,然后再应用上述规则
除法操作符(/
)
用于计算第一个操作数除以第二个操作数的商。跟乘法操作符一样,除法操作符针对特殊值也有一些特殊的行为:
- 如果操作数都是数值,则执行常规的除法运算,即两个正值相除是正值,两个负值相除也是正值,符号不同的值相除得到负值。如果 ECMAScript 不能表示商,则返回 Infinity 或 -Infinity。
- 如果有任一操作数是
NaN
,则返回NaN
- 如果是 Infinity 除以 Infinity,则返回
NaN
- 如果是 0 除以 0,则返回
NaN
- 如果是非 0 的有限值除以 0,则根据第一个操作数的符号返回 Infinity 或 -Infinity
- 如果是 Infinity 除以任何数值,则根据第二个操作数的符号返回 Infinity 或 -Infinity
- 如果有不是数值的操作数,则先用
Number()
函数将其转换为数值,然后再用上述规则
取模操作符(%
)
与其他乘性操作符一样,取模操作符对特殊值也有一些特殊的行为:
- 如果操作数是数值,则执行常规除法运算,返回余数
- 如果被除数是无限值,除数是有限值,则返回
NaN
- 如果被除数是有限值,除数是 0,则返回
NaN
- 如果是 Infinity 除以 Infinity,则返回
NaN
- 如果被除数是有限值,除数是无限值,则返回被除数
- 如果被除数是 0,除数不是 0,则返回 0
- 如果有不是数值的操作数,则先用
Number()
函数将其转换为数值,然后再用上述规则
指数操作符
ECMAScript 7 新增了指数操作符,Math.pow()
现在有了自己的操作符 **
,两者计算结果一样。
而且,指数操作符也有自己的指数赋值操作符 **=
,该操作符执行指数运算和结果的赋值操作。
1 |
|
加性操作符
加法操作符(+
)
如果两个操作数都是数值,加法操作符执行加法运算并根据如下规则返回结果:
- 如果有任一操作数是
NaN
,则返回NaN
- 如果是 Infinity 加 Infinity,则返回 Infinity
- 如果是 -Infinity 加 -Infinity,则返回 -Infinity
- 如果是 Infinity 加 -Infinity,则返回
NaN
- 如果是 +0 加 +0,则返回 +0
- 如果是 -0 加 +0,则返回 +0
- 如果是 -0 加 -0,则返回 -0
不过,如果有一个操作数是字符串,则要应用如下规则:
- 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面
- 如果只有一个操作数是字符串,则将另一个操作数转为字符串,再将两个字符串拼接在一起
如果有任一操作数是对象、数值或布尔值,则调用它们 toString()
方法以获取字符串,然后再应用关于字符串的规则。对于 undefined
和 null
,则调用 String()
函数,分别获取 undefined
和 null
。
ECMAScript 中最常犯的一个错误,就是忽略加法操作中涉及的数据类型。
减法操作符(-
)
减法操作符也有一组规则用于处理 ECMAScript 中不同类型之间的转换:
- 如果两个操作数都是数值,则执行数学减法运算并返回结果
- 如果有任一操作数是
NaN
,则返回NaN
- 如果是 Infinity 减 Infinity,则返回
NaN
- 如果是 -Infinity 减-Infinity,则返回
NaN
- 如果是 Infinity 减-Infinity,则返回 Infinity
- 如果是 -Infinity 减 Infinity,则返回 -Infinity
- 如果是 +0 减 +0,则返回 +0
- 如果是 +0 减 -0,则返回 -0
- 如果是 -0 减 -0,则返回 +0
- 若有任一操作数是字符串、布尔值、
null
或undefined
,则先使用Number()
将其转为数值,然后再根据前面的规则执行数学运算。若转换结果是NaN
,则结果是NaN
- 若有任一操作数是对象,则调用其
valueOf()
方法取得表示它的数值。如果该值是NaN
,则计算的结果是NaN
。如果对象没有valueOf()
方法,则调用其toString()
方法,然后再将得到的字符串转换为数值
关系操作符
关系操作符执行比较两个值的操作。
包括小于(<
)、大于(>
)、小于等于(<=
)、大于等于(>=
),这几个操作符返回布尔值。
与 ECMAScript 中的其他操作符一样,在将它们应用到不同数据类型时也会发生类型转换和其他行为:
- 如果操作数都是数值,则执行数值比较
- 如果操作数都是字符串,则逐个比较字符串中对应字符的编码
- 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较
- 如果有任一操作数是对象,则调用其
valueOf()
方法,取得结果后根据前面规则执行比较。如果没有valueOf()
方法,则调用toString()
方法,取得结果后根据前面规则比较 - 如果有任一操作数是布尔值,则将其转换为数值再执行比较
对字符串而言,关系操作符会比较字符串中对应字符的编码,而这些编码是数值。比较完后会返回布尔值。问题的关键在于大写字母的编码都小于小写字母的编码。如,字母 B 的编码是 66,字母 b 的编码是 98。要得到确实按字母顺序比较的结果,就必须把两者都转换为相同的大小写形式(全大写或全小写),然后再比较。
另一个奇怪的现象是在比较两个数值字符串的时候。在比较字符串 "23" < "3"
时返回 true
。因为两个操作数都是字符串,所以会逐个比较它们字符编码(字符”2”的编码是 50,而字符”3”的编码是 51)。不过如果有一个操作数是数值,那么比较的结果就对了,"23" < 3
时返回 false
。只要是数值和字符串比较,字符串就会先被转为数值,然后进行数值比较。对于数值字符串而言,这样能保证结果正确。
但如果字符串不能转换成数值呢?如果字符不能转换成任何有意义的数值只能转换为 NaN
,有一个规则,即任何关系操作符在涉及比较 NaN
时都返回 false
。在大多数比较的场景中,如果一个值不小于另一个值,那就一定大于或等于它。但在比较 NaN
时,无论是小于还是大于等于,比较的结果都会返回 false
。
相等操作符
判断两个变量是否相等是编程中最重要操作之一。
在比较字符串、数值和布尔值是否相等时过程都很直观。但是在比较两个对象是否相等时,情形就比较复杂。ECMAScript 中的相等和不相等操作符,原本在比较之前会执行类型转换,但是有人质疑这种转换是否应该发生。最终,ECMAScript 提供了两组操作符。第一组是等于和不等于,它们在比较之前执行转换。第二组是全等和不全等,它们在比较之前不执行转换。
等于和不等于
等于操作符(==
),如果操作数相等,则会返回 true
;不等于操作符(!=
),如果两个操作数不相等,则会返回 true
。这两个操作符会先进行类型转换(称为强制类型转换)再确定操作数是否相等。
在转换操作数的类型时,相等和不相等操作符遵循如下规则:
- 如果任一操作数是布尔值,则将其转换为数值再比较是否相等。
false
转换为 0,true
转换为 1 - 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等
- 如果一个操作数是对象,另一个操作数不是对象,则调用对象的 valueOf() 方法取得其原始值,再根据前面规则进行比较
在进行比较时,这两个操作符会遵循如下规则:
null
和undefined
相等null
和undefined
不能转换为其他类型的值再进行比较- 如果有任一操作数是
NaN
,则相等操作符返回false
,不相等操作符返回true
。记住:即使两个操作数都是NaN
,相等操作符也返回false
,因为按照规则,NaN
不等于NaN
- 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回
true
。否则,两者不相等
下表总结了一些特殊情况及比较的结果:
表达式 | 结果 |
---|---|
null == undefined |
true |
"NaN" == NaN |
false |
5 == NaN |
false |
NaN == NaN |
false |
NaN != NaN |
true |
false == 0 |
true |
true == 1 |
true |
true == 2 |
false |
undefined == 0 |
false |
null == 0 |
false |
"5" == 5 |
true |
全等和不全等
全等和不全等操作符与相等和不相等操作符类似,只不过在比较相等时不转换操作数。
全等操作符(===
),只有两个操作数在不转换的前提下相等才返回 true
。不全等操作(!==
),只有两个操作数在不转换的前提下不相等才返回 true
。由于相等和不相等操作符存在类型转换问题,推荐使用全等和不全等操作符。这样有助于在代码中保持数据类型的完整性
条件操作符
条件操作符是 ECMAScript 中用途最为广泛的操作符之一。
const variable = boolean_expression ? true_value : false_value;
如果 boolean_expression
是 true
,则赋 variable
值为 true_value
;如果 boolean_expression
是 false
,则赋 variable
值为 false_value
。
赋值操作符
简单赋值(=
),将右手边的值赋给左手边的变量。复合赋值使用乘性、加性或位操作符后跟等于号(=
)表示。
每个数学操作符以及其他一些操作符都有对应的复合赋值操作符:
- 乘后赋值(
*=
) - 除后赋值(
/=
) - 取模后赋值(
%=
) - 加后赋值(
+=
) - 减后赋值(
-=
) - 指数赋值(
**=
) - 左移后赋值(
<<=
) - 右移后赋值(
>>=
) - 无符号右移后赋值(
>>>=
)
这些操作符仅仅是简写语法,使用它们不会提升性能。
逗号操作符
逗号操作符可以用来在一条语句中执行多个操作。
let num1 = 1, num2 = 2, num3 = 3;
在一条语句中同时声明多个变量是逗号操作符最常用的场景。
let num = (5, 1, 4, 8, 0); // num 的值为 0
也可以使用逗号操作符来辅助赋值,赋值时最终会返回表达式中最后一个值。逗号操作符的这种使用场景并不多见,但这种行为的确存在。