术语:“一元运算符”,“二元运算符”,“运算元”

运算元

运算元 —— 运算符应用的对象。比如说乘法运算 5 * 2,有两个运算元:左运算元 5 和右运算元 2。有时候人们也称其为“参数”而不是“运算元”。

一元运算符

只有一个运算元,那么它是 一元运算符。比如说一元负号运算符(unary negation)-,它的作用是对数字进行正负转换:

  1. let x = 1;
  2. x = -x;
  3. alert( x ); // -1,一元负号运算符生效

二元运算符

如果一个运算符拥有两个运算元,那么它是 二元运算符。减号还存在二元运算符形式:

  1. let x = 1, y = 3;
  2. alert( y - x ); // 2,二元运算符减号做减运算

数学

Javascript支持以下数学运算:

  • 加法 +,
  • 减法 -,
  • 乘法 *,
  • 除法 /,
  • 取余 %,
  • 求幂 **.

前四个都很简单,而 % 和 ** 则需要说一说。

取余 %

取余运算符是 %,尽管它看起来很像百分数,但实际并无关联。
a % b 的结果是 a 整除 b 的 余数)。
例如:

  1. alert( 5 % 2 ); // 1,5 除以 2 的余数
  2. alert( 8 % 3 ); // 2,8 除以 3 的余数

求幂 **

求幂运算 a ** b 是 a 乘以自身 b 次。
例如:

  1. alert( 2 ** 2 ); // 4 (2 * 2,自乘 2 次)
  2. alert( 2 ** 3 ); // 8 (2 * 2 * 2,自乘 3 次)
  3. alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2,自乘 4 次)

在数学上,求幂的定义也适用于非整数。例如,平方根是以 1/2 为单位的求幂:

  1. alert( 4 ** (1/2) ); // 2(1/2 次方与平方根相同)
  2. alert( 8 ** (1/3) ); // 2(1/3 次方与立方根相同)

用二元运算符 + 连接字符串

通常,加号 + 用于求和。
但是如果加号 + 被应用于字符串,它将合并(连接)各个字符串:

  1. let s = "my" + "string";
  2. alert(s); // mystring

注意:只要任意一个运算元是字符串,那么另一个运算元也将被转化为字符串。
举个例子:

  1. alert( '1' + 2 ); // "12"
  2. alert( 2 + '1' ); // "21"

下面是一个更复杂的例子:

  1. alert(2 + 2 + '1' ); // "41",不是 "221"

在这里,运算符是按顺序工作。第一个 + 将两个数字相加,所以返回 4,然后下一个 + 将字符串 1 加入其中,所以就是 4 + ‘1’ = 41。
二元 + 是唯一一个以这种方式支持字符串的运算符。其他算术运算符只对数字起作用,并且总是将其运算元转换为数字。
下面是减法和除法运算的示例:

  1. alert( 6 - '2' ); // 4,将 '2' 转换为数字
  2. alert( '6' / '2' ); // 3,将两个运算元都转换为数字

数字转化,一元运算符 +

加号 + 有两种形式(一元运算符和二元运算符)
一元运算符加号,或者说,加号 + 应用于单个值,对数字没有任何作用
但是如果运算元不是数字,加号 + 则会将其转化为数字
例如:

  1. // 对数字无效
  2. let x = 1;
  3. alert( +x ); // 1
  4. let y = -2;
  5. alert( +y ); // -2
  6. // 转化非数字
  7. alert( +true ); // 1
  8. alert( +"" ); // 0

它的效果和 Number(…) 相同,但是更加简短。
对于字符串求和,二元运算符加号会把它们合并成字符串:

  1. let apples = "2";
  2. let oranges = "3";
  3. alert( apples + oranges ); // "23",二元运算符加号合并字符串

如果我们想把它们当做数字对待,我们需要转化它们,然后再求和:

  1. let apples = "2";
  2. let oranges = "3";
  3. // 在二元运算符加号起作用之前,所有的值都被转化为了数字
  4. alert( +apples + +oranges ); // 5
  5. // 更长的写法
  6. // alert( Number(apples) + Number(oranges) ); // 5

为什么一元运算符先于二元运算符作用于运算元?
这是由于它们拥有 更高的优先级

运算符优先级

如果一个表达式拥有超过一个运算符,执行的顺序则由 优先级 决定。
乘法比加法拥有 更高的优先级
圆括号拥有最高优先级。

在 JavaScript 中有众多运算符。每个运算符都有对应的优先级数字。数字越大,越先执行。如果优先级相同,则按照由左至右的顺序执行。
这是一个摘抄自 Mozilla 的 优先级表(你没有必要把这全记住,但要记住一元运算符优先级高于二元运算符):

优先级 名称 符号
17 一元加号 +
17 一元负号 -
16 求幂 **
15 乘号 *
15 除号 /
13 加号 +
13 减号 -
3 赋值符 =

赋值运算符

赋值运算符的优先级非常低。
这也是为什么,当我们赋值时,比如 x = 2 * 2 + 1,所有的计算先执行,然后 = 才执行,将计算结果存储到 x。

  1. let x = 2 * 2 + 1;
  2. alert( x ); // 5

赋值 = 返回一个值

在 JavaScript 中,大多数运算符都会返回一个值。这对于 + 和 - 来说是显而易见的,但对于 = 来说也是如此。
语句 x = value 将值 value 写入 x 然后返回 x
下面是一个在复杂语句中使用赋值的例子:

  1. let a = 1;
  2. let b = 2;
  3. let c = 3 - (a = b + 1);
  4. alert( a ); // 3
  5. alert( c ); // 0

上面这个例子,(a = b + 1) 的结果是赋给 a 的值(也就是 3)。然后该值被用于进一步的运算。

链式赋值(Chaining assignments)

另一个有趣的特性是链式赋值:

  1. let a, b, c;
  2. a = b = c = 2 + 2;
  3. alert( a ); // 4
  4. alert( b ); // 4
  5. alert( c ); // 4

链式赋值从右到左进行计算。首先,对最右边的表达式 2 + 2 求值,然后将其赋给左边的变量:c、b 和 a。最后,所有的变量共享一个值。
同样,出于可读性,最好将这种代码分成几行:

  1. c = 2 + 2;
  2. b = c;
  3. a = c;

原地修改

我们经常需要对一个变量做运算,并将新的结果存储在同一个变量中。
例如:

  1. let n = 2;
  2. n = n + 5;
  3. n = n * 2;

可以使用运算符 += 和 *= 来缩写这种表示。

  1. let n = 2;
  2. n += 5; // 现在 n = 7(等同于 n = n + 5)
  3. n *= 2; // 现在 n = 14(等同于 n = n * 2)
  4. alert( n ); // 14

所有算术和位运算符都有简短的“修改并赋值”运算符:/= 和 -= 等。
这类运算符的优先级与普通赋值运算符的优先级相同,所以它们在大多数其他运算之后执行:

  1. let n = 2;
  2. n *= 3 + 5;
  3. alert( n ); // 16 (右边部分先被计算,等同于 n *= 8)

自增/自减

对一个数进行加一、减一是最常见的数学运算符之一。
自增 ++ 将变量与 1 相加:

  1. let counter = 2;
  2. counter++; // 和 counter = counter + 1 效果一样,但是更简洁
  3. alert( counter ); // 3

自减 — 将变量与 1 相减:

  1. let counter = 2;
  2. counter--; // 和 counter = counter - 1 效果一样,但是更简洁
  3. alert( counter ); // 1

重要: 自增/自减只能应用于变量。试一下,将其应用于数值(比如 5++)则会报错。

运算符 ++ 和 — 可以置于变量前,也可以置于变量后。

  • 当运算符置于变量后,被称为“后置形式”:counter++。
  • 当运算符置于变量前,被称为“前置形式”:++counter。

前置形式返回一个新的值,但后置返回原来的值(做加法/减法之前的值)

前置形式:

  1. let counter = 1;
  2. let a = ++counter; // (*)
  3. alert(a); // 2

(*) 所在的行是前置形式 ++counter,对 counter 做自增运算,返回的是新的值 2。因此 alert 显示的是 2。
后置形式:

  1. let counter = 1;
  2. let a = counter++; // (*) 将 ++counter 改为 counter++
  3. alert(a); // 1

() 所在的行是后置形式 counter++,它同样对 counter 做加法,但是返回的是 旧值(做加法之前的值)。因此 alert 显示的是 1。
*总结:

如果自增/自减的值不会被使用,那么两者形式没有区别:

  1. let counter = 0;
  2. counter++;
  3. ++counter;
  4. alert( counter ); // 2,以上两行作用相同

如果我们想要对变量进行自增操作,并且 需要立刻使用自增后的值,那么我们需要使用前置形式:

  1. let counter = 0;
  2. alert( ++counter ); // 1

如果我们想要将一个数加一,但是我们想使用其自增之前的值,那么我们需要使用后置形式:

  1. let counter = 0;
  2. alert( counter++ ); // 0

位运算符

位运算符把运算元当做 32 位整数,并在它们的二进制表现形式上操作。
下面是位运算符:

  • 按位与 ( & )
  • 按位或 ( | )
  • 按位异或 ( ^ )
  • 按位非 ( ~ )
  • 左移 ( << )
  • 右移 ( >> )
  • 无符号右移 ( >>> )

当你需要了解它们的时候,可以阅读 MDN 上的 位操作符 章节。

逗号运算符

逗号运算符 , 是最少见最不常使用的运算符之一。
逗号运算符可以将语句分隔开,只返回最后的语句。
举个例子:

  1. let a = (1 + 2, 3 + 4);
  2. alert( a ); // 7(3 + 4 的结果)

这里,第一个语句 1 + 2 运行了,但是它的结果被丢弃了。随后计算 3 + 4,并且该计算结果被返回。

逗号运算符的优先级非常低 请注意逗号运算符的优先级非常低,比 = 还要低,因此上面你的例子中圆括号非常重要。 如果没有圆括号:a = 1 + 2, 3 + 4 会先执行 +,将数值相加得到 a = 3, 7,然后赋值运算符 = 执行, ‘a = 3’,然后逗号之后的数值 7 不会再执行,它被忽略掉了。相当于 (a = 1 + 2), 3 + 4。

为什么我们需要这样一个运算符,它只返回最后一个值呢?
有时候,人们会使用它把几个行为放在一行上来进行复杂的运算。
举个例子:

  1. // 一行上有三个运算符
  2. for (a = 1, b = 3, c = a * b; a < 10; a++) {
  3. ...
  4. }