布尔操作符
布尔操作符有 3 种:逻辑非、逻辑与和逻辑或。
逻辑非
逻辑非操作符由一个叹号(!)表示,可应用给 ECMAScript 中的任何值。它首先将操作数(任意数据类型)转换为布尔值,然后再对其取反。
它遵循如下规则:
- 如果操作数是对象,则返回 false。
- 如果操作数是空字符串,则返回 true。
- 如果操作数是非空字符串,则返回 false。
- 如果操作数是数值 0,则返回 true。
- 如果操作数是非 0 数值(包括 Infinity),则返回 false。
- 如果操作数是 null,则返回 true。
- 如果操作数是 NaN,则返回 true。
- 如果操作数是 undefined,则返回 true。
可以看下面的例子:
console.log(!false); // true
console.log(!"blue"); // false
console.log(!0); // true
console.log(!NaN); // true
console.log(!""); // true
console.log(!12345); // false
复制代码
同时使用两个叹号(!!),相当于调用了转型函数 Boolean():
console.log(!!"blue"); // true
console.log(!!0); // false
console.log(!!NaN); // false
console.log(!!""); // false
console.log(!!12345); // true
复制代码
逻辑与
逻辑与操作符由两个和号(&&)表示:
let result = true && false;
复制代码
遵循数学真值表。
如果有操作数不是布尔值,则逻辑与并不一定会返回布尔值。它遵循如下规则:
- 如果第一个操作数是对象,则返回第二个操作数。
- 如果第二个操作数是对象,则只有第一个操作数求值为 true 才会返回该对象。
- 如果两个操作数都是对象,则返回第二个操作数。
- 如果有一个操作数是 null,则返回 null。
- 如果有一个操作数是 NaN,则返回 NaN。
- 如果有一个操作数是 undefined,则返回 undefined。
逻辑与操作符是一种短路操作符,意思就是如果第一个操作数决定了结果,那么永远不会对第二个操作数求值。对逻辑与操作符来说,如果第一个操作数是 false,那么无论第二个操作数是什么值,结果也不可能等于 true。
let found = true;
let result = found && someUndeclaredVariable; // 这里会出错
console.log(result); // 不会执行这一行
复制代码
因为第一个操作数是 true,所以会返回第二个对象。而 someUndeclaredVariable 是未声明的变量,所以这里会报错。
当我们把第一个操作符改成 false:
let found = false;
let result = found && someUndeclaredVariable; // 不会出错
console.log(result); // 会执行
复制代码
因为第一个操作数是 false,所以会直接返回 false。这个时候第二个变量会被无视,所以也就不会报错。
逻辑或
逻辑或操作符由两个管道符(||)表示
let result = true || false;
复制代码
逻辑或也符合数学真值表的判定方法。
遇到有操作数不是布尔值时,遵循如下规则:
- 如果第一个操作数是对象,则返回第一个操作数。
- 如果第一个操作数求值为 false,则返回第二个操作数。
- 如果两个操作数都是对象,则返回第一个操作数。
- 如果两个操作数都是 null,则返回 null。
- 如果两个操作数都是 NaN,则返回 NaN。
- 如果两个操作数都是 undefined,则返回 undefined。
逻辑或操作符也具有短路的特性。只不过对逻辑或而言,第一个操作数求值为 true,第二个操作数就不会再被求值了。这里不再举例说明。
基于这个特性,可以避免给变量赋值 null 或 undefined。比如:
let myObject = preferredObject || backupObject;
复制代码
其中,preferredObject 变量包含首选的值,backupObject 变量包含备用的值。如果 preferredObject 不是 null,则它的值就会赋给 myObject;如果 preferredObject 是 null,则 backupObject 的值就会赋给 myObject。这种模式在 ECMAScript 代码中经常用于变量赋值。
相等操作符
ECMAScript 提供了两组相等操作符,可以按需使用。
第一组是等于和不等于,他们在比较之前执行转换。
第二组是全等和不全等,他们在比较之前不转换。
等于==和不等于!=
等于操作符用==表示,如果操作数相等则返回 true,否则返回 false。
不等于操作符用!=表示,如果操作数不相等则返回 true,否则返 false。
这两个操作符都会先进行类型转换(也叫强制类型转换)再确定操作数是否相等。
他们都遵循如下规则:
-
如果任一操作数是布尔值,则将其转换为数值再比较是否相等。false 转换为 0,true 转换
为 1。 -
如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否
相等。 -
如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf()方法取得其原始值,再
根据前面的规则进行比较。
在进行比较时,这两个操作符会遵循如下规则。
-
null 和 undefined 相等。
-
null 和 undefined 不能转换为其他类型的值再进行比较。
-
如果有任一操作数是 NaN,则相等操作符返回 false,不相等操作符返回 true。记住:即使两
个操作数都是 NaN,相等操作符也返回 false,因为按照规则,NaN 不等于 NaN。 -
如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,
则相等操作符返回 true。否则,两者不相等。
下面是特殊情况的总结:
表达式 | 结果 |
---|---|
null == undefined | true |
“NaN” == NaN | false |
5 == NaN | false |
NaN == NaN | false |
NaN != NaN | true |
false == 0 | true |
true = 1 | true |
true == 2 | false |
undefined == 0 | false |
null == 0 | false |
“5” == 5 | true |
全等和不全等
他们在比较相等时不转换操作数。所以他们也要判断数据类型是否相等。
另外,虽然 null == undefined 是 true(因为这两个值类似),但 null === undefined 是 false,因为它们不是相同的数据类型。
关系操作符
关系操作符包括小于(<)、大于(>)、小于等于(<=)和大于等于(>=)。
当两个变量都是数值时,按数学规则比较大小。
当有不同数据类型时,按如下规则:
- 如果操作数都是字符串,则逐个比较字符串中对应字符的编码。
- 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较。
- 如果有任一操作数是对象,则调用其 valueOf()方法,取得结果后再根据前面的规则执行比较。
- 如果没有 valueOf()操作符,则调用 toString()方法,取得结果后再根据前面的规则执行比较。
- 如果有任一操作数是布尔值,则将其转换为数值再执行比较。
在使用关系操作符比较两个字符串时,关系操作符会比较字符串中对应字符的编码。所以一般来说,“字母顺序靠后”的更大。但如果比较大小写字母,这个规则就不适用:
let result = "Brick" < "alphabet"; // true
复制代码
在这里,字符串”Brick”被认为小于字符串”alphabet”,因为字母 B 的编码是 66,字母 a 的编码是 97。所以要把他们都转成大写或小写再去比较才有意义:
let result = "Brick".toLowerCase() < "alphabet".toLowerCase(); // false
复制代码
在比较两个数值字符串也是这样:
let result = "23" < "3"; // true
复制代码
我们第一反映会觉得 23 大,但是因为是字符串,他们会比较“2”和“3”在编码里的数值。字符”2″的编码是 50,而字符”3″的编码是 51。所以后者更大。
但是如果其中一个是数值,那么另一个会先被转换成数值再比较:
let result = "23" < 3; // false
复制代码
当字符串不能转换成数值时。字符串首先会被转换为 NaN。而 NaN 和任何变量比较时都会返回 False。所以会有下面这样的神奇情况:
let result = "a" < 3; // 因为"a"会转换为NaN,所以结果是false
let result1 = NaN < 3; // false
let result2 = NaN >= 3; // false
复制代码