我们从学校中学到了很多运算符。它们包括加法 +、乘法 *、减法 - 等。
在本章中,我们将从简单的运算符开始,然后专注于学校算术中未涵盖的 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…)