# 比較運算符
## 概述
比較運算符用于比較兩個值的大小,然后返回一個布爾值,表示是否滿足指定的條件。
```javascript
2 > 1 // true
```
上面代碼比較`2`是否大于`1`,返回`true`。
> 注意,比較運算符可以比較各種類型的值,不僅僅是數值。
JavaScript 一共提供了8個比較運算符。
- `>` 大于運算符
- `<` 小于運算符
- `<=` 小于或等于運算符
- `>=` 大于或等于運算符
- `==` 相等運算符
- `===` 嚴格相等運算符
- `!=` 不相等運算符
- `!==` 嚴格不相等運算符
這八個比較運算符分成兩類:相等比較和非相等比較。兩者的規則是不一樣的,對于非相等的比較,算法是先看兩個運算子是否都是字符串,如果是的,就按照字典順序比較(實際上是比較 Unicode 碼點);否則,將兩個運算子都轉成數值,再比較數值的大小。
## 非相等運算符:字符串的比較
字符串按照字典順序進行比較。
```javascript
'cat' > 'dog' // false
'cat' > 'catalog' // false
```
JavaScript 引擎內部首先比較首字符的 Unicode 碼點。如果相等,再比較第二個字符的 Unicode 碼點,以此類推。
```javascript
'cat' > 'Cat' // true'
```
上面代碼中,小寫的`c`的 Unicode 碼點(`99`)大于大寫的`C`的 Unicode 碼點(`67`),所以返回`true`。
由于所有字符都有 Unicode 碼點,因此漢字也可以比較。
```javascript
'大' > '小' // false
```
上面代碼中,“大”的 Unicode 碼點是22823,“小”是23567,因此返回`false`。
## 非相等運算符:非字符串的比較
如果兩個運算子之中,至少有一個不是字符串,需要分成以下兩種情況。
**(1)原始類型值**
如果兩個運算子都是原始類型的值,則是先轉成數值再比較。
```javascript
5 > '4' // true
// 等同于 5 > Number('4')
// 即 5 > 4
true > false // true
// 等同于 Number(true) > Number(false)
// 即 1 > 0
2 > true // true
// 等同于 2 > Number(true)
// 即 2 > 1
```
上面代碼中,字符串和布爾值都會先轉成數值,再進行比較。
這里需要注意與`NaN`的比較。任何值(包括`NaN`本身)與`NaN`使用非相等運算符進行比較,返回的都是`false`。
```javascript
1 > NaN // false
1 <= NaN // false
'1' > NaN // false
'1' <= NaN // false
NaN > NaN // false
NaN <= NaN // false
```
**(2)對象**
如果運算子是對象,會轉為原始類型的值,再進行比較。
對象轉換成原始類型的值,算法是先調用`valueOf`方法;如果返回的還是對象,再接著調用`toString`方法,詳細解釋參見《數據類型的轉換》一章。
```javascript
var x = [2];
x > '11' // true
// 等同于 [2].valueOf().toString() > '11'
// 即 '2' > '11'
x.valueOf = function () { return '1' };
x > '11' // false
// 等同于 [2].valueOf() > '11'
// 即 '1' > '11'
```
兩個對象之間的比較也是如此。
```javascript
[2] > [1] // true
// 等同于 [2].valueOf().toString() > [1].valueOf().toString()
// 即 '2' > '1'
[2] > [11] // true
// 等同于 [2].valueOf().toString() > [11].valueOf().toString()
// 即 '2' > '11'
{ x: 2 } >= { x: 1 } // true
// 等同于 { x: 2 }.valueOf().toString() >= { x: 1 }.valueOf().toString()
// 即 '[object Object]' >= '[object Object]'
```
## 嚴格相等運算符
JavaScript 提供兩種相等運算符:`==`和`===`。
簡單說,它們的區別是相等運算符(`==`)比較兩個值是否相等,嚴格相等運算符(`===`)比較它們是否為“同一個值”。如果兩個值不是同一類型,嚴格相等運算符(`===`)直接返回`false`,而相等運算符(`==`)會將它們轉換成同一個類型,再用嚴格相等運算符進行比較。
本節介紹嚴格相等運算符的算法。
**(1)不同類型的值**
如果兩個值的類型不同,直接返回`false`。
```javascript
1 === "1" // false
true === "true" // false
```
上面代碼比較數值的`1`與字符串的“1”、布爾值的`true`與字符串`"true"`,因為類型不同,結果都是`false`。
**(2)同一類的原始類型值**
同一類型的原始類型的值(數值、字符串、布爾值)比較時,值相同就返回`true`,值不同就返回`false`。
```javascript
1 === 0x1 // true
```
上面代碼比較十進制的`1`與十六進制的`1`,因為類型和值都相同,返回`true`。
需要注意的是,`NaN`與任何值都不相等(包括自身)。另外,正`0`等于負`0`。
```javascript
NaN === NaN // false
+0 === -0 // true
```
**(3)復合類型值**
兩個復合類型(對象、數組、函數)的數據比較時,不是比較它們的值是否相等,而是比較它們是否指向同一個地址。
```javascript
{} === {} // false
[] === [] // false
(function () {} === function () {}) // false
```
上面代碼分別比較兩個空對象、兩個空數組、兩個空函數,結果都是不相等。原因是對于復合類型的值,嚴格相等運算比較的是,它們是否引用同一個內存地址,而運算符兩邊的空對象、空數組、空函數的值,都存放在不同的內存地址,結果當然是`false`。
如果兩個變量引用同一個對象,則它們相等。
```javascript
var v1 = {};
var v2 = v1;
v1 === v2 // true
```
注意,對于兩個對象的比較,嚴格相等運算符比較的是地址,而大于或小于運算符比較的是值。
```javascript
var obj1 = {};
var obj2 = {};
obj1 > obj2 // false
obj1 < obj2 // false
obj1 === obj2 // false
```
上面的三個比較,前兩個比較的是值,最后一個比較的是地址,所以都返回`false`。
**(4)undefined 和 null**
`undefined`和`null`與自身嚴格相等。
```javascript
undefined === undefined // true
null === null // true
```
由于變量聲明后默認值是`undefined`,因此兩個只聲明未賦值的變量是相等的。
```javascript
var v1;
var v2;
v1 === v2 // true
```
## 嚴格不相等運算符
嚴格相等運算符有一個對應的“嚴格不相等運算符”(`!==`),它的算法就是先求嚴格相等運算符的結果,然后返回相反值。
```javascript
1 !== '1' // true
// 等同于
!(1 === '1')
```
上面代碼中,感嘆號`!`是求出后面表達式的相反值。
## 相等運算符
相等運算符用來比較相同類型的數據時,與嚴格相等運算符完全一樣。
```javascript
1 == 1.0
// 等同于
1 === 1.0
```
比較不同類型的數據時,相等運算符會先將數據進行類型轉換,然后再用嚴格相等運算符比較。下面分成幾種情況,討論不同類型的值互相比較的規則。
**(1)原始類型值**
原始類型的值會轉換成數值再進行比較。
```javascript
1 == true // true
// 等同于 1 === Number(true)
0 == false // true
// 等同于 0 === Number(false)
2 == true // false
// 等同于 2 === Number(true)
2 == false // false
// 等同于 2 === Number(false)
'true' == true // false
// 等同于 Number('true') === Number(true)
// 等同于 NaN === 1
'' == 0 // true
// 等同于 Number('') === 0
// 等同于 0 === 0
'' == false // true
// 等同于 Number('') === Number(false)
// 等同于 0 === 0
'1' == true // true
// 等同于 Number('1') === Number(true)
// 等同于 1 === 1
'\n 123 \t' == 123 // true
// 因為字符串轉為數字時,省略前置和后置的空格
```
上面代碼將字符串和布爾值都轉為數值,然后再進行比較。具體的字符串與布爾值的類型轉換規則,參見《數據類型轉換》一章。
**(2)對象與原始類型值比較**
對象(這里指廣義的對象,包括數組和函數)與原始類型的值比較時,對象轉換成原始類型的值,再進行比較。
具體來說,先調用對象的`valueOf()`方法,如果得到原始類型的值,就按照上一小節的規則,互相比較;如果得到的還是對象,則再調用`toString()`方法,得到字符串形式,再進行比較。
下面是數組與原始類型值比較的例子。
```javascript
// 數組與數值的比較
[1] == 1 // true
// 數組與字符串的比較
[1] == '1' // true
[1, 2] == '1,2' // true
// 對象與布爾值的比較
[1] == true // true
[2] == true // false
```
上面例子中,JavaScript 引擎會先對數組`[1]`調用數組的`valueOf()`方法,由于返回的還是一個數組,所以會接著調用數組的`toString()`方法,得到字符串形式,再按照上一小節的規則進行比較。
下面是一個更直接的例子。
```javascript
const obj = {
valueOf: function () {
console.log('執行 valueOf()');
return obj;
},
toString: function () {
console.log('執行 toString()');
return 'foo';
}
};
obj == 'foo'
// 執行 valueOf()
// 執行 toString()
// true
```
上面例子中,`obj`是一個自定義了`valueOf()`和`toString()`方法的對象。這個對象與字符串`'foo'`進行比較時,會依次調用`valueOf()`和`toString()`方法,最后返回`'foo'`,所以比較結果是`true`。
**(3)undefined 和 null**
`undefined`和`null`只有與自身比較,或者互相比較時,才會返回`true`;與其他類型的值比較時,結果都為`false`。
```javascript
undefined == undefined // true
null == null // true
undefined == null // true
false == null // false
false == undefined // false
0 == null // false
0 == undefined // false
```
**(4)相等運算符的缺點**
相等運算符隱藏的類型轉換,會帶來一些違反直覺的結果。
```javascript
0 == '' // true
0 == '0' // true
2 == true // false
2 == false // false
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
' \t\r\n ' == 0 // true
```
上面這些表達式都不同于直覺,很容易出錯。因此建議不要使用相等運算符(`==`),最好只使用嚴格相等運算符(`===`)。
## 不相等運算符
相等運算符有一個對應的“不相等運算符”(`!=`),它的算法就是先求相等運算符的結果,然后返回相反值。
```javascript
1 != '1' // false
// 等同于
!(1 == '1')
```
- 前言
- 入門篇
- 導論
- 歷史
- 基本語法
- 數據類型
- 概述
- null,undefined 和布爾值
- 數值
- 字符串
- 對象
- 函數
- 數組
- 運算符
- 算術運算符
- 比較運算符
- 布爾運算符
- 二進制位運算符
- 其他運算符,運算順序
- 語法專題
- 數據類型的轉換
- 錯誤處理機制
- 編程風格
- console 對象與控制臺
- 標準庫
- Object 對象
- 屬性描述對象
- Array 對象
- 包裝對象
- Boolean 對象
- Number 對象
- String 對象
- Math 對象
- Date 對象
- RegExp 對象
- JSON 對象
- 面向對象編程
- 實例對象與 new 命令
- this 關鍵字
- 對象的繼承
- Object 對象的相關方法
- 嚴格模式
- 異步操作
- 概述
- 定時器
- Promise 對象
- DOM
- 概述
- Node 接口
- NodeList 接口,HTMLCollection 接口
- ParentNode 接口,ChildNode 接口
- Document 節點
- Element 節點
- 屬性的操作
- Text 節點和 DocumentFragment 節點
- CSS 操作
- Mutation Observer API
- 事件
- EventTarget 接口
- 事件模型
- Event 對象
- 鼠標事件
- 鍵盤事件
- 進度事件
- 表單事件
- 觸摸事件
- 拖拉事件
- 其他常見事件
- GlobalEventHandlers 接口
- 瀏覽器模型
- 瀏覽器模型概述
- window 對象
- Navigator 對象,Screen 對象
- Cookie
- XMLHttpRequest 對象
- 同源限制
- CORS 通信
- Storage 接口
- History 對象
- Location 對象,URL 對象,URLSearchParams 對象
- ArrayBuffer 對象,Blob 對象
- File 對象,FileList 對象,FileReader 對象
- 表單,FormData 對象
- IndexedDB API
- Web Worker
- 附錄:網頁元素接口
- a
- img
- form
- input
- button
- option
- video,audio