JavaScript高階副本:“==”隱藏下的型別轉換
拋磚引玉
按照正常的邏輯來說,我們判斷兩個值是否相等會遵循以下規則:
但是我看下面一組值:
[]==0 //true []==false //true []==!{} //true [10]==10 //true '0'==false //true ''==0 //true undefined==null //true !null==true //true
居然沒有按照我們的劇本走,那它比較規則又是什麼?下面我就來分析一波。
“==”的比較規則
首先我們先去ECMAScript5.1中文版( http://lzw.me/pages/ecmascrip... )找一下“==”的比較規則,如下:
1.若Type(x)與Type(y)相同, 則 a.若Type(x)為Undefined, 返回true。 b.若Type(x)為Null, 返回true。 c.若Type(x)為Number, 則 i.若x為NaN, 返回false。 ii.若y為NaN, 返回false。 iii.若x與y為相等數值, 返回true。 iv.若x 為 +0 且 y為−0, 返回true。 v.若x 為 −0 且 y為+0, 返回true。 vi返回false。 d.若Type(x)為String, 則當x和y為完全相同的字元序列(長度相等且相同字元在相同位置)時返回true。 否則, 返回false。 e.若Type(x)為Boolean, 當x和y為同為true或者同為false時返回true。 否則, 返回false。 f.當x和y為引用同一物件時返回true。否則,返回false。 2.若x為null且y為undefined, 返回true。 3.若x為undefined且y為null, 返回true。 4.若Type(x) 為 Number 且 Type(y)為String, 返回comparison x == ToNumber(y)的結果。 5.若Type(x) 為 String 且 Type(y)為Number,返回比較ToNumber(x) == y的結果。 6.若Type(x)為Boolean, 返回比較ToNumber(x) == y的結果。 7.若Type(y)為Boolean, 返回比較x == ToNumber(y)的結果。 8.若Type(x)為String或Number,且Type(y)為Object,返回比較x == ToPrimitive(y)的結果。 9.若Type(x)為Object且Type(y)為String或Number, 返回比較ToPrimitive(x) == y的結果。 10.返回 false
看完ECMAScript5.1中文版的介紹之後,相信很多小夥伴的心情應該是這樣的:
別看上面說的有點花裡胡哨的,其實我們可以用很簡單的話來總結出來。由於本篇文章核心是“==”是如何進行型別轉換,我就總結一下型別不同的情況下“==”是如何比較的。
- 當資料型別為Boolean型別或者String型別時,比較時需要轉換成Number型別。
- 當資料型別為引用型別時,需要轉換成原始資料型別。當轉換後的原始資料型別為Boolean型別或者String型別,則繼續轉換成Number型別。
- undefined和null跟任何值在“==”下都返回false,但二者在“==”下返回true。
具體流程圖如下:
備註:
Javascript的資料型別可以分為以下兩種:
- 原始資料型別(null、undefined、Number、String、Boolean、Symbol(Es6才引入的))
- 引用型別 (Object)
Boolean型別、String型別轉換成Number型別的規則(ToNumber)
Boolean型別
Boolean | Number |
---|---|
true | 1 |
false | 0 |
String型別
標準的數字格式
如果是標準的數字格式,轉換成Number型別相比不用多說,比如下面這幾個栗子:chestnut::
"123" => 123 "12.34" => 12.34 "0.12" => 0.12 "-12.34" => -12.34
非標準的數字格式
但是如果是非標準的資料格式,要分兩種情況來考慮:
- 第一種:只包含數字並且最多隻有1個點。
- 第二種:包含非數字以及含有多個1個點。
只包含數字並且最多隻有1個點
這種情況下會首先進行補0和去0的操作,下面看幾個栗子:chestnut::
"01234" => 1234 ".1" => 0.1 "12." => 12 "1.000" => 1 "-02.30" => -2.3
包含非數字以及含有多個1個點
這種情況下統統返回NaN,意為“Not a Number”,這裡我們要注意一下,NaN還是Number型別,下面看幾個栗子:chestnut::
"123aa4" => NaN "哈嘍,DD" => NaN typeof NaN => "numer"
引用型別轉換成原始資料型別的規則(ToPrimitive)
在介紹轉換規則之前,首先我們我們介紹一下引用型別都帶有的兩個方法:
- Object.prototype.toString
- Object.prototype.valueOf
這二者都可以將引用型別轉換成原始資料型別,接下來我們對二者做一個詳細的介紹:
Object.prototype.toString
MDN是這樣解釋的:
The toString() method returns a string representing the object.(toString()這個方法返回一個代表這個物件的字串)
舉個栗子:chestnut::
const obj = {} console.log(String(obj))//"[object Object]" obj.toString = function(){ return 'Hello,Teacher Cang!' } console.log(String(obj)) //"Hello,Teacher Cang!"
Object.prototype.valueOf
MDN是這樣解釋的:
The valueOf() method returns the primitive value of the specified object.( valueOf()這個方法返回這個物件的原始資料值)
舉個栗子:chestnut::
const obj = {} console.log(Number(obj)) //NaN obj.valueOf = function(){ return 12345; } console.log(Number(obj)) //12345
valueOf和toString的優先順序
關於這二者的優先順序,在不同的情況下有著不同的優先順序,下面我們根據不同情況介紹一下。
ToNumber
看個栗子:chestnut::
const obj = { toString(){ console.log('呼叫了toString'); return 'Hello,Teacher Cang!'; }, valueOf(){ console.log('呼叫了valueOf'); return 12345; } } console.log(Number(obj)); 控制檯輸出結果: >>>>>>>>>>>>>>>>>> 呼叫了valueOf 12345
通過上面的程式碼的執行結果,我們得出這麼一個結論:
在ToNumber情況下,valueOf的優先順序大於toString。
接下里我們看這麼一種情況,如果valueOf返回的並不是原始資料型別會怎麼樣。
const obj = { toString(){ console.log('呼叫了toString'); return 12345; }, valueOf(){ console.log('呼叫了valueOf'); return {}; } } console.log(Number(obj)); 控制檯輸出結果: >>>>>>>>>>>>>>>>>> 呼叫了valueOf 呼叫了toString 12345
從上面的執行結果中,我們可以得出這麼一個結論:
在ToNumber情況下,如果valueOf返回的不是原始資料型別,則會自動呼叫toString。
打破砂鍋問到底,再來一個非常極端的情況,如果說valueOf和toString返回的都不是原始資料型別,這時又該怎麼樣呢?同樣,我們繼續看一下執行結果:
const obj = { toString(){ console.log('呼叫了toString'); return {}; }, valueOf(){ console.log('呼叫了valueOf'); return {}; } } console.log(Number(obj)); 控制檯輸出結果: >>>>>>>>>>>>>>>>>> 呼叫了valueOf 呼叫了toString Uncaught TypeError: Cannot convert object to primitive value
從上面的執行結果中,我們再次可以得出這麼一個結論:
在ToNumber情況下,如果valueOf和toString返回的都不是原始資料型別,那麼js會丟擲異常,提示無法將引用型別轉換原始資料型別。
根據三組程式碼的執行的結果,我們最後總結一下:
在ToNumber情況下,js會優先呼叫valueOf,如果valueOf返回的不是原始資料型別,則會接著呼叫toString,如果toString返回的也不是原始資料型別,js會丟擲一個異常,提示無法將引用型別轉換原始資料型別。
具體流程圖如下:
ToString
看個栗子:chestnut::
const obj = { toString(){ console.log('呼叫了toString'); return 'Hello,Teacher Cang!'; }, valueOf(){ console.log('呼叫了valueOf'); return 12345; } } console.log(String(obj)); 控制檯輸出結果: >>>>>>>>>>>>>>>>>> 呼叫了toString Hello,Teacher Cang!
通過上面的程式碼的執行結果,我們得出這麼一個結論:
在ToString情況下,toString的優先順序大於valueOf。
同樣我們看一下,toString返回的值不是原始資料型別時會怎樣:
const obj = { toString(){ console.log('呼叫了toString'); return {}; }, valueOf(){ console.log('呼叫了valueOf'); return 'Hello,Teacher Cang!'; } } console.log(String(obj)); 控制檯輸出結果: >>>>>>>>>>>>>>>>>> 呼叫了toString 呼叫了valueOf Hello,Teacher Cang!
根據執行結果我們可以得出:
在ToString情況下,如果toString返回的不是原始資料型別,則會自動呼叫valueOf。
最後我們看一下極端情況,二者返回的都不是原始資料型別:
const obj = { toString(){ console.log('呼叫了toString'); return {}; }, valueOf(){ console.log('呼叫了valueOf'); return {}; } } console.log(String(obj)); 控制檯輸出結果: >>>>>>>>>>>>>>>>>> 呼叫了toString 呼叫了valueOf Uncaught TypeError: Cannot convert object to primitive value
我們又發現:
在ToString情況下,如果toString和valueOf返回的都不是原始資料型別,那麼js會丟擲異常,提示無法將引用型別轉換原始資料型別。
我們將三個結論綜合一下:
在ToString情況下,js會優先呼叫toString,如果toString返回的不是原始資料型別,則會接著呼叫valueOf,如果valueOf返回的也不是原始資料型別,js會丟擲一個異常,提示無法將引用型別轉換原始資料型別。
具體流程圖如下:
“==”下的valueOf和toString的優先順序
從文章前面我們總結出來“==”的比較流程來看,引用型別轉換成原始資料型別之後,如果是Sting型別的話,還要再次轉成Number型別,因此“==”下的引用型別轉換原始資料型別過程中,遵循ToNumber的優先順序規則。
const obj = { toString(){ console.log('呼叫了toString'); return 'Hello,Teacher Cang!'; }, valueOf(){ console.log('呼叫了valueOf'); return 12345; } } console.log(obj==12345); 控制檯輸出結果: >>>>>>>>>>>>>>>>>> 呼叫了valueOf true
const obj = { toString(){ console.log('呼叫了toString'); return 12345; }, valueOf(){ console.log('呼叫了valueOf'); return {}; } } console.log(obj==12345); 控制檯輸出結果: >>>>>>>>>>>>>>>>>> 呼叫了valueOf 呼叫了toString true
const obj = { toString(){ console.log('呼叫了toString'); return {}; }, valueOf(){ console.log('呼叫了valueOf'); return {}; } } console.log(obj==12345); 控制檯輸出結果: >>>>>>>>>>>>>>>>>> 呼叫了toString 呼叫了valueOf Uncaught TypeError: Cannot convert object to primitive value
“==”之外的型別轉換
“==”號只涉及到了 ToPrimitive 和 ToNumber 這兩種轉換, ToBoolean 和 ToString 這兩個沒有涉及到的我們也介紹一下。
ToBoolean
對於ToBoolean,我們只需要記住幾個特例是轉成false的,其餘的皆為true。
Boolean('') => false Boolean(undefined) => false Boolean(null) => false Boolean(0) => false
ToString
Number to String
Number轉String之前,首先會做一個去0和補0的操作,然後再去轉成String型別。
String(1.234) => "1.234" String(NaN) => "NaN" String(.1234) => "0.1234" String(1.23400) => "1.234"
Boolean to String
String(true) => "true" String(false) => "false"
null和undefined to String
String(null) => "null" String(undefined) => "undefined"
引用型別 to String
引用型別先ToPrimitive轉換成原始資料型別,若轉換後的原始資料型別不是String型別,再做String型別的轉換。
const obj = { toString(){ console.log('呼叫了toString'); return true; } } console.log(String(obj)) 控制檯輸出結果: >>>>>>>>>>>>>>>>>> 呼叫了toString "true"
總結
“==”下的型別轉換,要分為兩種情況來考慮。第一種,原始資料型別;第二種,引用型別。原始資料型別中String型別和Boolean型別是需要轉換成Number型別再去比較的,而引用型別則需要先轉換成原始資料型別再進行後續的轉換。搞清整個流程之後,我們再去理解“==”涉及到的ToNumber和ToPrimitive是如何進行轉換的。按照這樣子的理解步驟走,我們對“==”隱藏下的型別轉換會有比較清晰的認識。另外,“==”不涉及到ToString和ToBoolean的型別轉換,在文章的後面部分我也加上去了,希望可以給小夥伴們一個更加全面的型別轉換的認識。最後附上完整的“==”型別轉換的流程圖: