JavaScript高階副本:「==」隱藏下的類型轉換

拋磚引玉

按照正常的邏輯來講,咱們判斷兩個值是否相等會遵循如下規則:

可是我看下面一組值:ecmascript

[]==0 //true
[]==false //true
[]==!{} //true
[10]==10 //true
'0'==false //true
''==0 //true
undefined==null //true 
!null==true //true


竟然沒有按照咱們的劇本走,那它比較規則又是什麼?下面我就來分析一波。spa

「==」的比較規則

首先咱們先去ECMAScript5.1中文版( http://lzw.me/pages/ecmascrip... )找一下「==」的比較規則,以下:prototype

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中文版的介紹以後,相信不少小夥伴的心情應該是這樣的:

別看上面說的有點花裏胡哨的,其實咱們能夠用很簡單的話來總結出來。因爲本篇文章核心是「==」是如何進行類型轉換,我就總結一下類型不一樣的狀況下「==」是如何比較的。code

  • 當數據類型爲Boolean類型或者String類型時,比較時須要轉換成Number類型。
  • 當數據類型爲引用類型時,須要轉換成原始數據類型。當轉換後的原始數據類型爲Boolean類型或者String類型,則繼續轉換成Number類型。
  • undefined和null跟任何值在「==」下都返回false,但兩者在「==」下返回true。

具體流程圖以下:對象

備註:ip

Javascript的數據類型能夠分爲如下兩種:ci

  • 原始數據類型(null、undefined、Number、String、Boolean、Symbol(Es6才引入的))
  • 引用類型 (Object)

Boolean類型、String類型轉換成Number類型的規則(ToNumber)

Boolean類型

Boolean Number
true 1
false 0

String類型

標準的數字格式

若是是標準的數字格式,轉換成Number類型相比不用多說,好比下面這幾個栗子🌰:rem

"123" => 123
"12.34" => 12.34
"0.12" => 0.12
"-12.34" => -12.34

非標準的數字格式

可是若是是非標準的數據格式,要分兩種狀況來考慮:字符串

  1. 第一種:只包含數字而且最多隻有1個點。
  2. 第二種:包含非數字以及含有多個1個點。

只包含數字而且最多隻有1個點

這種狀況下會首先進行補0和去0的操做,下面看幾個栗子🌰:get

"01234" => 1234
".1" => 0.1
"12." => 12
"1.000" => 1
"-02.30" => -2.3

包含非數字以及含有多個1個點

這種狀況下通通返回NaN,意爲「Not a Number」,這裏咱們要注意一下,NaN仍是Number類型,下面看幾個栗子🌰:

"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()這個方法返回一個表明這個對象的字符串)

舉個栗子🌰:

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()這個方法返回這個對象的原始數據值)

舉個栗子🌰:

const obj = {}
console.log(Number(obj)) //NaN
obj.valueOf = function(){
    return 12345;
}
console.log(Number(obj)) //12345

valueOf和toString的優先級

關於這兩者的優先級,在不一樣的狀況下有着不一樣的優先級,下面咱們根據不一樣狀況介紹一下。

ToNumber

看個栗子🌰:

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

看個栗子🌰:

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

「==」以外的類型轉換

「==」號只涉及到了ToPrimitiveToNumber這兩種轉換,ToBooleanToString這兩個沒有涉及到的咱們也介紹一下。

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的類型轉換,在文章的後面部分我也加上去了,但願能夠給小夥伴們一個更加全面的類型轉換的認識。最後附上完整的「==」類型轉換的流程圖:

相關文章
相關標籤/搜索