基础运算
基础运算
术语
在正式开始前,我们先简单浏览一下常用术语。
运算元 —— 运算符应用的对象。比如说乘法运算
5 * 2
,有两个运算元:左运算元5
和右运算元2
。有时候人们也称其为“参数”而不是“运算元”。如果一个运算符对应的只有一个运算元,那么它是 一元运算符。比如说一元负号运算符(unary negation)
-
,它的作用是对数字进行正负转换:let x = 1; x = -x; // 符号反转运算符
如果一个运算符拥有两个运算元,那么它是 二元运算符。减号还存在二元运算符形式:
let x = 1, y = 3; alert( y - x ); // 2,二元运算符减号做减运算
数学
支持以下数学运算:
- 加法
+
, - 减法
-
, - 乘法
*
, - 除法
/
, - 取余
%
, - 求幂
**
类型转换
用二元运算符 + 连接字符串
如果加号 +
被应用于字符串,它将合并(连接)各个字符串:
let s = "my" + "string";
alert(s); // mystring
注意:只要任意一个运算元是字符串,那么另一个运算元也将被转化为字符串。
alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"
alert(2 + 2 + '1' ); // "41",不是 "221"
alert('1' + 2 + 2); // "122",不是 "14"
下面是减法和除法运算的示例:
alert( 6 - '2' ); // 4,将 '2' 转换为数字
alert( '6' / '2' ); // 3,将两个运算元都转换为数字
数字转化,一元运算符 +
还有一种 +
是一元运算符。如果运算元不是数字,加号 +
则会将其转化为数字。
// 转化非数字
alert( +true ); // 1
alert( +"" ); // 0
二元运算符加号会把它们合并成字符串:
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
一元运算符先于二元运算符作用于运算元
赋值运算符
我们知道赋值符号 =
也是一个运算符。从优先级表中可以看到它的优先级非常低,只有 2
。
这也是为什么,当我们赋值时,比如 x = 2 * 2 + 1
,所有的计算先执行,然后 =
才执行,将计算结果存储到 x
。
let x = 2 * 2 + 1;
alert( x ); // 5
赋值 = 返回一个值了,语句 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
)。然后该值被用于进一步的运算。
不过,请不要写这样的代码。这样的技巧绝对不会使代码变得更清晰或可读。
let a, b, c;
a = b = c = 2 + 2;
alert( a ); // 4
alert( b ); // 4
alert( c ); // 4
运算符的优先级
如果一个表达式拥有超过一个运算符,执行的顺序则由 优先级 决定。换句话说,所有的运算符中都隐含着优先级顺序。
在 JavaScript 中有众多运算符。每个运算符都有对应的优先级数字。数字越大,越先执行。如果优先级相同,则按照由左至右的顺序执行。
记住一元运算符优先级高于二元运算符
优先级 | 名称 | 符号 |
---|---|---|
... | ... | ... |
15 | 一元加号 | + |
15 | 一元负号 | - |
14 | 求幂 | ** |
13 | 乘号 | * |
13 | 除号 | / |
12 | 加号 | + |
12 | 减号 | - |
... | ... | ... |
2 | 赋值符 | = |
... | ... | ... |
原地修改
let n = 2;
n += 5; // 现在 n = 7(等同于 n = n + 5)
n *= 2; // 现在 n = 14(等同于 n = n * 2)
alert( n ); // 14
let counter = 2;
counter++; // 和 counter = counter + 1 效果一样,但是更简洁
alert( counter ); // 3
counter = 2;
--counter;
位运算符
位运算符把运算元当做 32 位整数,并在它们的二进制表现形式上操作。
这些运算符不是 JavaScript 特有的。大部分的编程语言都支持这些运算符。
下面是位运算符:
- 按位与 (
&
) - 按位或 (
|
) - 按位异或 (
^
) - 按位非 (
~
) - 左移 (
<<
) - 右移 (
>>
) - 无符号右移 (
>>>
)
逻辑运算符
||
(或)&&
(与)!
(非)??
(空值合并运算符)
这些运算符和c++一样,存在短路的情况。
我们需要注意的是,在逻辑运算符中的返回值。js中的逻辑运算不一定返回 ture或者false,还会返回其它类型。
或运算寻找第一个真值
给定几个参与或运算的值:
result = value1 || value2 || value3;
或运算符 ||
做了如下的事情:
- 从左到右依次计算操作数。
- 处理每一个操作数时,都将其转化为布尔值。如果结果是
true
,就停止计算,返回这个操作数的初始值。 - 如果所有的操作数都被计算过(也就是,转换结果都是
false
),则返回最后一个操作数。
返回的值是操作数的初始形式,不会做布尔转换。
换句话说,一个或运算 ||
的链,将返回第一个真值,如果不存在真值,就返回该链的最后一个值。
例如:
alert( 1 || 0 ); // 1(1 是真值)
alert( null || 1 ); // 1(1 是第一个真值)
alert( null || 0 || 1 ); // 1(第一个真值)
alert( undefined || null || 0 ); // 0(都是假值,返回最后一个值)
// 短路用法
let firstName = "";
let lastName = "";
let nickName = "SuperCoder";
alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder
与运算寻找第一个假值
也存在短路
!
非
两个非运算 !!
有时候用来将某个值转化为布尔类型:
alert( !!"non-empty string" ); // true
alert( !!null ); // false
空值合并运算符
This is a recent addition to the language. Old browsers may need polyfills.
由于它对待 null
和 undefined
的方式类似,所以在本文中我们将使用一个特殊的术语对其进行表示。为简洁起见,当一个值既不是 null
也不是 undefined
时,我们将其称为“已定义的(defined)”。
a ?? b
的结果是:
- 如果
a
是已定义的,则结果为a
, - 如果
a
不是已定义的,则结果为b
。
换句话说,如果第一个参数不是 null/undefined
,则 ??
返回第一个参数。否则,返回第二个参数。
我们可以使用我们已知的运算符重写 result = a ?? b
,像这样:
result = (a !== null && a !== undefined) ? a : b;
用法案例
let user;
alert(user ?? "匿名"); // 匿名(user 未定义)
// 当 height 的值为 null 或 undefined 时,将 height 的值设置为 100
height = height ?? 100;
||
返回第一个 真 值。??
返回第一个 已定义的 值。
换句话说,||
无法区分 false
、0
、空字符串 ""
和 null/undefined
。
出于安全原因,JavaScript 禁止将 ??
运算符与 &&
和 ||
运算符一起使用,除非使用括号明确指定了优先级。
逗号运算符
逗号运算符 ,
是最少见最不常使用的运算符之一。有时候它会被用来写更简短的代码,因此为了能够理解代码,我们需要了解它。
逗号运算符能让我们处理多个语句,使用 ,
将它们分开。每个语句都运行了,但是只有最后的语句的结果会被返回。
举个例子:
let a = (1 + 2, 3 + 4);
alert( a ); // 7(3 + 4 的结果)
但是通常它并不能提升代码的可读性,使用它之前,我们要想清楚。
值的比较
- 相等性检查
==
, 不会发生数字和null之间的转换 - 严格相等性检查
===
,不会发生转换 - 普通比较符
> < >= <=
,会发生转换
常规比较
alert( 2 > 1 ); // true(正确)
alert( 2 == 1 ); // false(错误)
alert( 2 != 1 ); // true(正确)
// string 也可比较,字符串是按 Unicode 编码顺序个进行比较的。
alert( 'Z' > 'A' ); // true
alert( 'Glow' > 'Glee' ); // true
alert( 'Bee' > 'Be' ); // true
当对不同类型的值进行比较时,JavaScript 会首先将其转化为数字(number)再判定大小。
alert( '2' > 1 ); // true,字符串 '2' 会被转化为数字 2
alert( '01' == 1 ); // true,字符串 '01' 会被转化为数字 1
严格相等
普通的相等性检查 ==
存在一个问题,它不能区分出 0
和 false
, 也同样无法区分空字符串和 false
**严格相等运算符 ===
在进行比较时不会做任何的类型转换。**换句话说,如果 a
和 b
属于不同的数据类型,那么 a === b
不会做任何的类型转换而立刻返回 false
。
对 null 和 undefined 进行比较
null/undefined 会被转化为数字:null 被转化为 0,undefined 被转化为 NaN。
alert( null == undefined ); // true, JavaScript 存在一个特殊的规则,会判定它们非严格下相等。
alert( null === undefined ); // false
null vs 0
// 进行值的比较时,`null` 会被转化为数字,因此它被转化为了 0
alert( null > 0 ); // (1) false
alert( null == 0 ); // (2) false
alert( null >= 0 ); // (3) true
相等性检查 ==
和普通比较符 > < >= <=
的代码逻辑是相互独立的。
这就是为什么(3)中 null >= 0
返回值是 true,(1)中 null > 0
返回值是 false。
undefined
undefined
不应该被与其他值进行比较:
alert( undefined > 0 ); // false (1)
alert( undefined < 0 ); // false (2)
alert( undefined == 0 ); // false (3)
原因如下:
(1)
和(2)
都返回false
是因为undefined
在比较中被转换为了NaN
,而NaN
是一个特殊的数值型值,它与任何值进行比较都会返回false
。(3)
返回false
是因为这是一个相等性检查,而undefined
只与null
相等,不会与其他值相等。
避免问题
我们为何要研究上述示例?我们需要时刻记得这些古怪的规则吗?不,其实不需要。虽然随着代码写得越来越多,我们对这些规则也都会烂熟于胸,但是我们需要更为可靠的方法来避免潜在的问题:
- 除了严格相等
===
外,其他但凡是有undefined/null
参与的比较,我们都需要格外小心。 - 除非你非常清楚自己在做什么,否则永远不要使用
>= > < <=
去比较一个可能为null/undefined
的变量。对于取值可能是null/undefined
的变量,请按需要分别检查它的取值情况。