我们从中学里了解到过好多运算符,比如说减号 +、乘号 *、减号 - 等。
在本章中,我们将从简单的运算符开始,然后侧重介绍 JavaScript 特有的方面,这些是在中学中学习的语文所没有囊括的。
术语:“一元运算符”,“二元运算符”,“运算元”
在即将开始前,我们先简单浏览一下常用术语。
数学
支持以下物理运算:
前四个都很简单,而 % 和 ** 则须要说一说。
取余 %
取余运算符是 %,尽管它看起来很像百分率,但实际并无关联。
a % b 的结果是 a 整除 b 的 余数)。
例如:
alert( 5 % 2 ); // 1,5 除以 2 的余数
alert( 8 % 3 ); // 2,8 除以 3 的余数
求幂 **
求幂运算 a ** b 将 a 提升至 a 的 b 次幂。
在物理中我们将其表示为 ab。
例如:
alert( 2 ** 2 ); // 2² = 4
alert( 2 ** 3 ); // 2³ = 8
alert( 2 ** 4 ); // 2⁴ = 16
就像在物理估算中一样,幂运算也适用于非整数。
例如,平方根是指数为 的幂运算:
alert( 4 ** (1/2) ); // 2(1/2 次方与平方根相同)
alert( 8 ** (1/3) ); // 2(1/3 次方与立方根相同)
用二元运算符 + 连接字符串
我们来看一些中学算术未涉及的 JavaScript 运算符的特点。
通常,加号 + 用于求和。
但是若果减号 + 被应用于字符串,它将合并(连接)各个字符串:
let s = "my" + "string";
alert(s); // mystring
注意:只要任意一个运算元是字符串,那么另一个运算元也将被转化为字符串。
举个反例:
alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"
你看,第一个运算元和第二个运算元,哪个是字符串并不重要。
下面是一个更复杂的事例:
alert(2 + 2 + '1' ); // "41",不是 "221"
在这儿,运算符是按次序工作。第一个 + 将两个数字相乘,所以返回 4,然后下一个 + 将字符串 1 加入其中,所以就是 4 + '1' = '41'。
alert('1' + 2 + 2); // "122",不是 "14"
这里,第一个操作数是一个字符串,所以编译器将其他两个操作数也视为了字符串。2 被与 '1' 连接到了一起,也就是像 '1' + 2 = "12" 然后 "12" + 2 = "122" 这样。
二元 + 是惟一一个以这些方法支持字符串的运算符。其他算术运算符只对数字起作用,并且总是将其运算元转换为数字。
下面是加法和乘法运算的示例:
alert( 6 - '2' ); // 4,将 '2' 转换为数字
alert( '6' / '2' ); // 3,将两个运算元都转换为数字
数字转化,一元运算符 +
加号 + 有两种方式。一种是前面我们刚才讨论的二元运算符,还有一种是一元运算符。
一元运算符减号,或者说,加号 + 应用于单个值,对数字没有任何作用。但是若果运算元不是数字,加号 + 则会将其转化为数字。
例如:
// 对数字无效
let x = 1;
alert( +x ); // 1
let y = -2;
alert( +y ); // -2
// 转化非数字
alert( +true ); // 1
alert( +"" ); // 0
它的疗效和 Number(...) 相同,但是愈发简练。
我们常常会有将字符串转化为数字的需求。比如,如果我们正在从 HTML 表单中取值,通常得到的都是字符串。如果我们想对它们求和,该如何办?
二元运算符减号会把它们合并成字符串:
let apples = "2";
let oranges = "3";
alert( apples + oranges ); // "23",二元运算符加号合并字符串
如果我们想把它们当作数字对待,我们须要转化它们,然后再求和:
let apples = "2";
let oranges = "3";
// 在二元运算符加号起作用之前,所有的值都被转化为了数字
alert( +apples + +oranges ); // 5
// 更长的写法
// alert( Number(apples) + Number(oranges) ); // 5
从一个数学家的视角来看,大量的减号可能很奇怪。但是从一个程序员的视角,没哪些好奇怪的:一元运算符减号首先起作用,它们将字符串转为数字,然后二元运算符减号对它们进行求和。
为什么一元运算符先于二元运算符作用于运算元?接下去我们将讨论到,这是因为它们拥有 更高的优先级。
运算符优先级
如果一个表达式拥有超过一个运算符,执行的次序则由 优先级 决定。换句话说,所有的运算符中都蕴涵着优先级次序。
从中学开始,我们就晓得在表达式 1 + 2 * 2 中,乘法先于乘法估算。这就是一个优先级问题。乘法比除法拥有 更高的优先级。
圆括号拥有最高优先级,所以假如我们对现有的运算次序不满意,我们可以使用圆括号来更改运算次序,就像这样:(1 + 2) * 2。
在 JavaScript 中有诸多运算符。每个运算符都有对应的优先级数字。数字越大,越先执行。如果优先级相同,则根据由左至右的次序执行。
这是一个摘录自 Mozilla 的 优先级表(你没有必要把这全记住,但要记住一元运算符优先级低于二元运算符):
优先级
名称
符号
…
…
…
15
一元减号
15
一元减号
14
求幂
**
13
乘号
13
除号
12
加号
12
减号
…
…
…
赋值符
…
…
…
我们可以看见,“一元减号运算符”的优先级是 15,高于“二元减号运算符”的优先级 12。这也是为何表达式 "+apples + +oranges" 中的一元减号先生效,然后才是二元乘法。
赋值运算符
我们晓得形参符号 = 也是一个运算符。从优先级表中可以见到它的优先级十分低,只有 2。
这也是为何,当我们形参时,比如 x = 2 * 2 + 1,所有的估算先执行,然后 = 才执行,将估算结果储存到 x。
let x = 2 * 2 + 1;
alert( x ); // 5
赋值 = 返回一个值
= 是一个运算符,而不是一个有着“魔法”作用的语言结构。
在 JavaScript 中,所有运算符就会返回一个值。这对于 + 和 - 来说是显而易见的,但对于 = 来说也是这么。
语句 x = value 将值 value 写入 x 然后返回 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 库中看见它。
不过,请不要写这样的代码。这样的方法绝对不会使代码显得更清晰或可读。
链式形参(Chaining assignments)
另一个有趣的特点是链式形参:
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; // 现在 n = 7(等同于 n = n + 5)
n *= 2; // 现在 n = 14(等同于 n = n * 2)
alert( n ); // 14
所有算术和位运算符都有简略的“修改并形参”运算符:/= 和 -= 等。
这类运算符的优先级与普通赋值运算符的优先级相同,所以它们在大多数其他运算以后执行:
let n = 2;
n *= 3 + 5;
alert( n ); // 16 (右边部分先被计算,等同于 n *= 8)
自增/自减
对一个数进行加一、减一是最常见的物理运算符之一。
所以,对此有一些专门的运算符:
重要:
自增/自减只能应用于变量。试一下,将其应用于数值(比如 5++)则会报错。
运算符 ++ 和 -- 可以放在变量前,也可以放在变量后。
两者都做同一件事:将变量 counter 与 1 相加。
那么它们有区别吗?有,但只有当我们使用 ++/-- 的返回值时才会看见区别。
详细点说。我们晓得,所有的运算符都有返回值。自增/自减也不例外。前置方式返回一个新的值,但前置返回原先的值(做除法/减法之前的值)。
为了直观见到区别,看下边的事例:
let counter = 1;
let a = ++counter; // (*)
alert(a); // 2
(*) 所在的行是后置方式 ++counter,对 counter 做自增运算,返回的是新的值 2。因此 alert 显示的是 2。
下面让我们瞧瞧前置方式:
let counter = 1;
let a = counter++; // (*) 将 ++counter 改为 counter++
alert(a); // 1
(*) 所在的行是前置方式 counter++,它同样对 counter 做减法,但是返回的是 旧值(做减法之前的值)。因此 alert 显示的是 1。
总结:
自增/自减和其它运算符的对比
++/-- 运算符同样可以在表达式内部使用。它们的优先级比绝大部分的算数运算符要高。
举个反例:
let counter = 1;
alert( 2 * ++counter ); // 4
与下方反例对比:
let counter = 1;
alert( 2 * counter++ ); // 2,因为 counter++ 返回的是“旧值”
尽管从技术层面上来说可行,但是这样的写法会增加代码的可阅读性。在一行上做空个操作 —— 这样并不好。
当阅读代码时,快速的视觉“纵向”扫描会很容易漏掉 counter++,这样的自增操作并不显著。
我们建议用“一行一个行为”的模式:
let counter = 1;
alert( 2 * counter );
counter++;
位运算符
位运算符把运算元当作 32 位整数js中运算符的优先级,并在它们的二进制表现形式上操作。
这些运算符不是 JavaScript 特有的。大部分的编程语言都支持这种运算符。
下面是位运算符:
这些运算符甚少被使用,一般是我们须要在最低级别(位)上操作数字时才使用。我们不会很快用到这种运算符,因为在 Web 开发中极少使用它们,但在个别特殊领域中,例如密码学,它们很有用。当你须要了解它们的时侯,可以阅读 MDN 上的 位操作符 章节。
逗号运算符
逗号运算符 , 是最稀少最不常使用的运算符之一。有时候它会被拿来写更简略的代码,因此为了才能理解代码,我们须要了解它。
逗号运算符能让我们处理多个句子,使用 , 将它们分开。每个句子都运行了,但是只有最后的句子的结果会被返回。
举个反例:
let a = (1 + 2, 3 + 4);
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。
为什么我们须要这样一个运算符,它只返回最后一个值呢?
有时候,人们会使用它把几个行为置于一行上来进行复杂的运算。
举个反例:
// 一行上有三个运算符
for (a = 1, b = 3, c = a * b; a < 10; a++) {
...
}
这样的方法在许多 JavaScript 框架中都有使用,这也是为何我们谈到它。但是一般它并不能提高代码的可读性,使用它之前,我们要想清楚。
任务前置运算符和后置运算符
重要程度: 5
以下代码中变量 a、b、c、d 的最终值分别是多少?
let a = 1, b = 1;
let c = ++a; // ?
let d = b++; // ?
解决方案
赋值结果
重要程度: 3
下面这段代码运行完成后,代码中的 a 和 x 的值是多少?
let a = 2;
let x = 1 + (a *= 2);
解决方案
类型转换
重要程度: 5
下面这种表达式的结果是哪些?
"" + 1 + 0
"" - 1 + 0
true + false
6 / "3"
"2" * "3"
4 + 5 + "px"
"#34; + 4 + 5
"4" - 2
"4px" - 2
" -9 " + 5
" -9 " - 5
null + 1
undefined + 1
" \t \n" - 2
好好思索一下,把它们写出来之后和答案比较一下。
解决方案
修正乘法
重要程度: 5
这里有一段代码,要求用户输入两个数字并显示它们的总和。
它的运行结果不正确。下面事例中的输出是 12(对于默认的 prompt 的值)。
为什么会这样?修正它。结果应当是 3。
let a = prompt("First number?", 1);
let b = prompt("Second number?", 2);
alert(a + b); // 12
解决方案
原因是 prompt 以字符串的方式返回用户的输入。
所以变量的值分别为 "1" 和 "2"。
let a = "1"; // prompt("First number?", 1);
let b = "2"; // prompt("Second number?", 2);
alert(a + b); // 12
我们应当做的是,在 + 之前将字符串转换为数字。例如,使用 Number() 或在 prompt 前加 +。
例如,就在 prompt 之前加 +:
let a = +prompt("First number?", 1);
let b = +prompt("Second number?", 2);
alert(a + b); // 3
或在 alert 中:
let a = prompt("First number?", 1);
let b = prompt("Second number?", 2);
alert(+a + +b); // 3
在最新的代码中js中运算符的优先级,同时使用一元和二元的 +。看起来很有趣,不是吗?