我们从学校中学到了很多运算符。它们包括加法 +
、乘法 *
、减法 -
等。
在本章中,我们将从简单的运算符开始,然后专注于学校算术中未涵盖的 JavaScript 特定方面。
术语:“一元”、“二元”、“操作数”
在继续之前,让我们掌握一些常用术语。
-
操作数 – 是运算符所应用的对象。例如,在
5 * 2
的乘法中,有两个操作数:左操作数是5
,右操作数是2
。有时,人们将它们称为“参数”而不是“操作数”。 -
如果运算符只有一个操作数,则该运算符为一元运算符。例如,一元取反
-
会反转数字的符号let x = 1; x = -x; alert( x ); // -1, unary negation was applied
-
如果运算符有两个操作数,则该运算符为二元运算符。减号也以二元形式存在
let x = 1, y = 3; alert( y - x ); // 2, binary minus subtracts values
形式上,在上面的示例中,我们有两个共享相同符号的不同运算符:取反运算符(一元运算符,用于反转符号)和减法运算符(二元运算符,用于从一个数字中减去另一个数字)。
数学
支持以下数学运算
- 加法
+
, - 减法
-
, - 乘法
*
, - 除法
/
, - 取余
%
, - 幂运算
**
。
前四个运算符比较简单,而 %
和 **
需要稍作解释。
取余 %
取余运算符 %
与百分比无关,尽管它看起来像百分号。
a % b
的结果是 a
除以 b
的整数除法的 余数。
例如
alert( 5 % 2 ); // 1, the remainder of 5 divided by 2
alert( 8 % 3 ); // 2, the remainder of 8 divided by 3
alert( 8 % 4 ); // 0, the remainder of 8 divided by 4
幂运算 **
幂运算符 a ** b
将 a
提升到 b
的幂。
在学校数学中,我们将其写为 ab。
例如
alert( 2 ** 2 ); // 2² = 4
alert( 2 ** 3 ); // 2³ = 8
alert( 2 ** 4 ); // 2⁴ = 16
与数学运算一样,幂运算符也适用于非整数。
例如,平方根是 ½ 的幂运算
alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root)
alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root)
使用二元 + 进行字符串连接
让我们了解一下 JavaScript 运算符超越学校算术运算的功能。
通常,加号运算符 +
对数字求和。
但是,如果将二元 +
应用于字符串,它会合并(连接)它们
let s = "my" + "string";
alert(s); // mystring
请注意,如果任何一个操作数是字符串,那么另一个操作数也会被转换为字符串。
例如
alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"
请看,第一个操作数是字符串还是第二个操作数并不重要。
这是一个更复杂的例子
alert(2 + 2 + '1' ); // "41" and not "221"
在这里,运算符一个接一个地工作。第一个 +
对两个数字求和,因此它返回 4
,然后下一个 +
向其添加字符串 1
,因此它就像 4 + '1' = '41'
。
alert('1' + 2 + 2); // "122" and not "14"
在这里,第一个操作数是字符串,编译器也将其他两个操作数视为字符串。2
连接到 '1'
,因此它就像 '1' + 2 = "12"
和 "12" + 2 = "122"
。
二元 +
是唯一以这种方式支持字符串的运算符。其他算术运算符只适用于数字,并且总是将其操作数转换为数字。
以下是减法和除法的演示
alert( 6 - '2' ); // 4, converts '2' to a number
alert( '6' / '2' ); // 3, converts both operands to numbers
数字转换,一元 +
加号 +
有两种形式:我们上面使用的二元形式和一元形式。
一元加号,或者换句话说,应用于单个值的加号运算符 +
,对数字不起作用。但是,如果操作数不是数字,一元加号会将其转换为数字。
例如
// No effect on numbers
let x = 1;
alert( +x ); // 1
let y = -2;
alert( +y ); // -2
// Converts non-numbers
alert( +true ); // 1
alert( +"" ); // 0
它实际上与 Number(...)
做同样的事情,但更简洁。
经常需要将字符串转换为数字。例如,如果我们从 HTML 表单字段中获取值,它们通常是字符串。如果我们想对它们求和呢?
二元加号会将它们作为字符串相加
let apples = "2";
let oranges = "3";
alert( apples + oranges ); // "23", the binary plus concatenates strings
如果我们想将它们视为数字,我们需要先转换,然后求和
let apples = "2";
let oranges = "3";
// both values converted to numbers before the binary plus
alert( +apples + +oranges ); // 5
// the longer variant
// alert( Number(apples) + Number(oranges) ); // 5
从数学家的角度来看,大量加号可能看起来很奇怪。但从程序员的角度来看,这没什么特别的:一元加号先应用,它们将字符串转换为数字,然后二元加号对它们求和。
为什么一元加号在二元加号之前应用于值?正如我们即将看到的,那是因为它们具有更高的优先级。
运算符优先级
如果一个表达式有多个运算符,执行顺序由它们的优先级定义,或者换句话说,由运算符的默认优先级顺序定义。
从学校我们都知道,表达式 1 + 2 * 2
中的乘法应该在加法之前计算。这正是优先级问题。乘法被认为比加法具有更高的优先级。
括号会覆盖任何优先级,因此如果我们对默认顺序不满意,我们可以使用括号来更改它。例如,写 (1 + 2) * 2
。
JavaScript 中有很多运算符。每个运算符都有一个对应的优先级编号。编号较大的运算符先执行。如果优先级相同,执行顺序是从左到右。
以下是 优先级表 的摘录(你不必记住这个,但请注意一元运算符高于相应二元运算符)
优先级 | 名称 | 符号 |
---|---|---|
… | … | … |
14 | 一元加号 | + |
14 | 一元取反 | - |
13 | 指数 | ** |
12 | 乘法 | * |
12 | 除法 | / |
11 | 加法 | + |
11 | 减法 | - |
… | … | … |
2 | 赋值 | = |
… | … | … |
正如我们所看到的,“一元加号”的优先级为 14
,高于“加法”(二元加号)的 11
。这就是为什么在表达式 "+apples + +oranges"
中,一元加号在加法之前起作用。
赋值
请注意,赋值 =
也是一个运算符。它在优先级表中列出,优先级非常低,为 2
。
这就是为什么当我们分配一个变量,比如 x = 2 * 2 + 1
时,首先进行计算,然后计算 =
,将结果存储在 x
中。
let x = 2 * 2 + 1;
alert( x ); // 5
赋值 = 返回一个值
=
作为运算符,而不是“神奇的”语言结构,有一个有趣的含义。
JavaScript 中的所有运算符都返回一个值。对于 +
和 -
来说很明显,但对于 =
也是如此。
调用 x = value
将 value
写入 x
然后返回它。
这是一个使用赋值作为更复杂表达式一部分的示例
let a = 1;
let b = 2;
let c = 3 - (a = b + 1);
alert( a ); // 3
alert( c ); // 0
在上面的示例中,表达式 (a = b + 1)
的结果是分配给 a
的值(即 3
)。然后将其用于进一步的计算。
有趣的代码,不是吗?我们应该了解它的工作原理,因为有时我们在 JavaScript 库中会看到它。
不过,请不要这样编写代码。这样的技巧肯定不会使代码更清晰或更易读。
链接赋值
另一个有趣的功能是链接赋值的能力
let a, b, c;
a = b = c = 2 + 2;
alert( a ); // 4
alert( b ); // 4
alert( c ); // 4
链接赋值从右到左计算。首先,计算最右边的表达式 2 + 2
,然后将其分配给左边的变量:c
、b
和 a
。最后,所有变量共享一个值。
同样,为了可读性,最好将此类代码分成几行
c = 2 + 2;
b = c;
a = c;
这样更容易阅读,尤其是在快速浏览代码时。
就地修改
我们经常需要对变量应用运算符并将新结果存储在同一个变量中。
例如
let n = 2;
n = n + 5;
n = n * 2;
可以使用运算符 +=
和 *=
来缩短此表示法
let n = 2;
n += 5; // now n = 7 (same as n = n + 5)
n *= 2; // now n = 14 (same as n = n * 2)
alert( n ); // 14
所有算术和位运算符都存在简短的“修改并赋值”运算符:/=
、-=
等。
此类运算符具有与普通赋值相同的优先级,因此它们在大多数其他计算之后运行
let n = 2;
n *= 3 + 5; // right part evaluated first, same as n *= 8
alert( n ); // 16
增量/减量
将数字增加或减少 1 是最常见的数字运算之一。
因此,有专门的运算符
-
增量
++
将变量增加 1let counter = 2; counter++; // works the same as counter = counter + 1, but is shorter alert( counter ); // 3
-
减量
--
将变量减少 1let counter = 2; counter--; // works the same as counter = counter - 1, but is shorter alert( counter ); // 1
增量/减量只能应用于变量。尝试在值(如 5++
)上使用它会产生错误。
运算符 ++
和 --
可以放在变量之前或之后。
- 当运算符位于变量之后时,它处于“后缀形式”:
counter++
。 - 当运算符位于变量之前时,它是“前缀形式”:
++counter
。
这两个语句都执行相同操作:将 counter
增加 1
。
有什么区别吗?是的,但只有在使用 ++/--
的返回值时才能看到它。
让我们澄清一下。众所周知,所有运算符都会返回一个值。增量/减量也不例外。前缀形式返回新值,而后缀形式返回旧值(在增量/减量之前)。
为了了解差异,这里有一个示例
let counter = 1;
let a = ++counter; // (*)
alert(a); // 2
在行(*)
中,前缀形式++counter
对counter
进行增量并返回新值2
。因此,alert
显示2
。
现在,让我们使用后缀形式
let counter = 1;
let a = counter++; // (*) changed ++counter to counter++
alert(a); // 1
在行(*)
中,后缀形式counter++
也对counter
进行增量,但返回旧值(在增量之前)。因此,alert
显示1
。
总结一下
-
如果不使用增量/减量的结果,则使用哪种形式没有区别
let counter = 0; counter++; ++counter; alert( counter ); // 2, the lines above did the same
-
如果我们想要增加一个值并立即使用运算符的结果,我们需要前缀形式
let counter = 0; alert( ++counter ); // 1
-
如果我们想要对一个值进行增量但使用其前一个值,我们需要后缀形式
let counter = 0; alert( counter++ ); // 0
运算符++/--
也可以在表达式中使用。它们的优先级高于大多数其他算术运算。
例如
let counter = 1;
alert( 2 * ++counter ); // 4
与比较
let counter = 1;
alert( 2 * counter++ ); // 2, because counter++ returns the "old" value
虽然在技术上是正确的,但这种表示法通常会降低代码的可读性。一行执行多个操作 - 不好。
在阅读代码时,快速“垂直”扫视很容易错过类似counter++
的内容,并且变量增加并不明显。
我们建议采用“一行 - 一个动作”的风格
let counter = 1;
alert( 2 * counter );
counter++;
按位运算符
按位运算符将参数视为 32 位整数,并在其二进制表示的级别上工作。
这些运算符不是 JavaScript 特有的。它们在大多数编程语言中都受支持。
运算符列表
- AND (
&
) - OR (
|
) - XOR (
^
) - NOT (
~
) - LEFT SHIFT (
<<
) - RIGHT SHIFT (
>>
) - ZERO-FILL RIGHT SHIFT (
>>>
)
当我们需要在最低(按位)级别上摆弄数字时,这些运算符的使用非常少见。我们很快不需要这些运算符,因为 Web 开发几乎不需要它们,但在某些特殊领域,例如密码学,它们很有用。当有需要时,你可以在 MDN 上阅读按位运算符章节。
逗号
逗号运算符,
是最稀有、最不寻常的运算符之一。有时,它用于编写更简洁的代码,因此我们需要了解它才能理解正在发生的事情。
逗号运算符允许我们求值多个表达式,用逗号,
将它们分开。每个表达式都会求值,但只有最后一个表达式的结果才会返回。
例如
let a = (1 + 2, 3 + 4);
alert( a ); // 7 (the result of 3 + 4)
这里,第一个表达式1 + 2
求值,其结果被丢弃。然后,3 + 4
求值并作为结果返回。
请注意,逗号运算符的优先级非常低,低于 =
,因此在上述示例中,括号非常重要。
如果不使用括号:a = 1 + 2, 3 + 4
会首先计算 +
,将数字相加得到 a = 3, 7
,然后赋值运算符 =
将 a = 3
赋值,其余部分将被忽略。这就像 (a = 1 + 2), 3 + 4
。
我们为什么要使用一个运算符,除了最后一个表达式之外,它会丢弃所有内容?
有时,人们在更复杂的结构中使用它,以便在一行中放置多个操作。
例如
// three operations in one line
for (a = 1, b = 3, c = a * b; a < 10; a++) {
...
}
许多 JavaScript 框架中都使用了此类技巧。这就是我们提到它们的原因。但通常它们不会提高代码的可读性,因此在使用它们之前我们应该仔细考虑。
评论
<code>
标记,对于多行 - 将其包装在<pre>
标记中,对于 10 行以上 - 使用沙盒(plnkr、jsbin、codepen…)