javascript是一門很是奇特的語言,它有時候奇特的會讓人懷疑人生。好比讓咱們看一下下面的一些奇葩例子:javascript
false == '0' //true '哇' false == 0 //true '哦' false == '' //true '噢' false == [] //true '啥?' 0 == '' //true 'what?' 0 == [] //true 0 == '0' //true [] == '0' //false 'why?' [] == '' //true //-----------更驚訝的是--------------- [] == ![] //true 'WTF!' [2] == 2 //true '' == [null] //true 0 == '\n' //true 我還能說什麼呢? false == '\n' //true
還有許多能夠列出來嚇你一跳的例子,別懷疑我是隨便編出來騙你的。當時我在瀏覽器運行這些時,我都懷疑我之前學得是假的js。若是要形容我當時的表情的話,你想一下黑人小哥的表情就能明白我當時是有多懷疑人生。
好,如今讓咱們先喝杯水壓壓驚,暫時忘記前面那些奇葩的例子。咱們首先了解一下js中有關類型轉換的知識。html
學過js的應該都瞭解js是一門弱類型語言。你在聲明一個變量的時候沒有告訴它是什麼類型,因而在程序運行時,你可能不知不覺中就更改了變量的類型。可能有些是你故意改的,另外一些可能並非你的本意,可是無論怎樣你都不可避免的會遇到類型轉換(強制或隱含)。讓咱們看一下下面的列子:java
var a = '1'; var b= Number(a); // b=1; +a; // 1; b + ''; // '1';
你們應該都知道答案,不少人在代碼中或多或少都會用到這些方法,而且都明白其中發生了值的類型轉換,可是大家是否有深刻了解js內部在類型轉換時作了哪些操做呢?算法
咱們首先來了解強制轉換爲Boolean類型時,發生了什麼操做。在用調用Boolean(a)或者!a等操做將值轉換爲Boolean類型時,js內部會調用ToBoolean方法來進行轉換,該方法定義瞭如下規則:瀏覽器
argument的類型 | 轉換的結果 |
---|---|
Undefined | false |
Null | false |
Boolean | argument |
Number | 若是argument是 +0、-0、NaN, 返回false; 不然返回true. |
String | 若是arguments是空字符串(長度爲0)返回false,不然返回true |
Object | true |
Symbol(ES6新增類型) | true |
從這個列表中咱們簡單歸納一下就是隻要argument的值是(undefined、null、+0、-0、NaN、''(空字符串)以及false))這7個裏的其中一個,那轉換以後返回的是false,其餘都爲true。js專門把這7個值放到一個falsy列表中,其他值都放在truthy列表。安全
ToNumber顧名思義即把其它類型轉換爲Number類型(js內部調用的方法,外部沒法訪問到),ECMAScript官方也專門給出了轉換規則:app
argument的類型 | 轉換的結果 |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | false爲+0,true爲1 |
Number | 返回argument |
Object | 執行如下步驟:讓primValue成爲ToPrimitive(argument, hint Number)的返回值,再調用ToNumber(primValue)返回。 |
Symbol(ES6新增類型) | 拋出TypeError異常. |
從列表能夠明顯看到少了一個String類型轉換爲Number的規則。由於String轉Number,js內部有很是複雜的判斷,我這裏面不詳細說轉換的細節,有興趣的能夠看一ECMAScript官方的說明。只要知道它與肯定Number字面量值的算法類似,可是要注意一下細節:prototype
在這裏特別說明一下
StrWhiteSpace:在js中StrWhiteSpace包含WhiteSpace(空白符)和LineTerminator(終止符)。
StrNumericLiteral:能夠理解爲包含Infinity和數字的字符串集合。
StringNumericLiteral:包含StrNumericLiteral和StrWhiteSpace的集合code
Unicode Code Point | name |
---|---|
U+0009 | 製表符<TAB> |
U+000B | 垂直方向的製表符<VT> |
U+000C | 換頁符<FF> |
U+0020 | 空格符<SP> |
U+00A0 | 不換行空格符 <NBSP> |
U+FEFF | 零寬度不換行空格符 <ZWNBSP> |
其餘種類的「Zs」(分隔符,空白) | Unicode 「Space_Separator」<USP> |
ECMAScript WhiteSpace有意排除具備Unicode「White_Space」屬性但未在類別「Space_Separator」(「Zs」)中分類的全部代碼點。htm
Zs列表
我這邊列出了Unicode其它「Zs」的列表,感興趣的能夠了解一下:
Unicode Code Point | name |
---|---|
U+1680 | OGHAM SPACE MARK |
U+180E | MONGOLIAN VOWEL SEPARATOR |
U+2000 | EN QUAD |
U+2001 | EM QUAD |
U+2002 | EN SPACE |
U+2003 | EM SPACE |
U+2004 | THREE-PER-EM SPACE |
U+2005 | FOUR-PER-EM SPACE |
U+2006 | SIX-PER-EM SPACE |
U+2007 | FIGURE SPACE |
U+2008 | PUNCTUATION SPACE |
U+2009 | THIN SPACE |
U+200A | NARROW NO-BREAK SPACE |
U+202F | FIGURE SPACE |
U+205F | MEDIUM MATHEMATICAL SPACE |
U+3000 | IDEOGRAPHIC SPACE |
Unicode Code Point | name |
---|---|
U+000A | 換行符<LF> |
U+000D | 回車<CR> |
U+2028 | 行分隔符<LS> |
U+2029 | 段分隔符<PS> |
上面的過程說的很抽象,不是很容易理解,咱們來看一下具體的列子:
Number(''); //0 empty Number(' '); //0 多個空格 Number('\u0009'); //0 製表符也能夠用Number('\t')表示 Number( ); //0 換行符也能夠用Number('\n')或Number('\u000A')表示 Number('000010'); //10 1前面的多個0被忽略 Number(' 10 '); //10 string先後多個StrWhiteSpace Number('\u000910\u0009'); //10 string先後有製表符 Number('ab'); //NaN
StrNumericLiteral中的其它進制的數字與十進制有類似的規則,但轉化的Number值是十進制下的值:
Number('0b10'); //2 (二進制) Number('0o17'); //15 (八進制) Number('0xA'); //10 (十六進制)
還有說明一點是十進制下數字的科學計數法顯示的字符串也能經過ToNumber轉換爲Number類型:
Number('1.2e+21'); //1.2e+21 Number('1.2e-21'); //1.2e-21
轉換爲String類型的規則以下:
argument的類型 | 轉換的結果 |
---|---|
Undefined | 'undefined' |
Null | 'null' |
Boolean | false爲'false',true爲true' |
String | argument |
Object | 執行如下步驟:讓primValue成爲ToPrimitive(argument, hint String)的返回值,再調用ToString(primValue)返回。 |
Symbol(ES6新增類型) | 拋出TypeError異常. |
一樣的在表中我也沒有列出Number類型轉換爲String類型的規則,Number轉String並非簡單的在數字先後加上‘或「就好了(即便看起來是這樣),裏面涉及到了複雜的數學算法,我不細說(好吧主要是我沒有特別理解,具體算法能夠看文檔),在這裏我只列出幾種特殊狀況:
假設Number的值爲m:
咱們在上面ToNumber和ToString方法中注意到Object類型轉換爲Number和String時都會調用ToPrimitive方法。該方法接受一個input輸入參數和一個可選的PreferredType參數。PreferredType是用來決定當某個對象可以轉換爲多個基本類型時該返回什麼類型。但是ToPrimitive內部到底是如何操做來返回Number或String類型的呢?若是要深刻探究其具體的操做步驟可能花大半天也不能徹底理清,裏面包含了各類方法的調用以及複雜的邏輯判斷還有各類安全檢測,我不仔細深刻下去。我這邊假設全部的判斷都按正常流程走,全部安全機制都經過不報錯誤,那麼一個對象轉換爲Number或String就能夠歸納爲如下幾個判斷:
@@toPrimitive是Symbol類型,是Symbol.toPrimitive的簡寫,ES6以前沒有Symbol類型,因此只需判斷toString和valueOf方法。
我這邊用幾個例子來解釋ToPrimitive的運行過程
var a = { [Symbol.toPrimitive]: (hint)=>{ if(hint==='number'){ return 1; }else if(hint==='string'){ return 'Symbol.toPrimitive'; }else if(hint==='default'){ return 2; }else{ throw TypeError('不能轉換爲String和Number以外的類型值'); //防止內部出現錯誤 } }, toString: () => 'toString', valueOf: () => 3 }; Number(a); //1 hint爲'number' String(a); //'Symbol.toPrimitive' hint爲'string' a + '1'; //'21' a在進行+操做符時hint爲'default',由於程序不知道你是作字符串相加仍是數值相加 a + 1; //3 +a; //1 此時hint爲'number',爲何hint不是'default',+a實際上內部進行ToNumber轉換,-、*、/操做符相似 //刪除a中Symbol.toPrimitive屬性 delete a[Symbol.toPrimitive]; Number(a); //3 調用valueOf方法 String(a); //'toString' 調用toString方法 a + 1; //4 結果不是'toString1'是由於js內部先判斷valueOf方法 //刪除a中valueOf屬方法 delete a['valueOf']; Number(a); //NaN 返回的'toString'不能轉換爲有效數字 String(a); //'toString' 1 + a; //'1toString' //重寫a中的toString方法 a.toString = () = > a; //返回了a對象 Number(a); //TypeError String(a); //TypeError 1 + a; //TypeError
上面例子看出Object類型在轉換爲String和Number時有可能會出現各類各樣的狀況。爲此咱們最好永遠不要重寫對象中的valueOf或者toString方法,以防出現意想不到的結果,若是你重寫了方法那麼你就要格外當心了。
Object.prototype.toString= () => 1; 1 + {}; //2 看到了嗎?永遠不要重寫Object中的內置方法,最好也不要在子對象中覆蓋Object的內置方法。
在此咱們對js中強制轉換時發生的過程基本捋了一遍,接下來咱們來了解一下相等操做符兩邊發生了什麼。
ECMAScript官方對(==)操做的說法是Abstract Equality Comparison(抽象的相等比較),它對x==y定義了下面一些規則:
Strict Equality Comparison(嚴格的相等比較)對x===y定義下列規則:
若是x的類型是Number:
- 若是x或y是NaN,返回false。 - 若是x和y數值相同,返回true。 - 若是x是+0,y是-0,返回true。 - 若是x是-0,y是+0,返回true。 - 其餘返回false。
提到(===)操做符,咱們不等不說一個方法Object.is(a,b),該方法也是比較兩個值是否同樣,但它比(===)更嚴格。它們之間的區別在於若是x和y是NaN,返回true。若是x是+0,y是-0,返回false,若是x是-0,y是+0,返回false。
到這裏類型轉換和相等比較的介紹就告一段落了,如今咱們從新回過頭去看一下最開始的幾個奇特例子,你會發現它們之間的關係比較是如此的正常。我就拿([] == ![])進行講解,按照操做符優先級比較,先運行![],它的值爲false,這時等式變成([] == false);按(==)的規則7對false進行ToNumber操做,值變爲0,這時等式變爲([] == 0);按(==)的規則9對[]進行ToPrimitive操做,調用Array上的toString方法,返回'',這時等式變爲('' == 0);按(==)的規則5對''進行ToNumber操做,值變爲0,這時等式是(0==0)。咱們最終得出結論([] == ![])是對的。
咱們看一下下面的例子:
1 + {}; //'1[object object]' {} + 1; //1 ({} + 1); //'[object object]1'
咱們發現第一和第三個表達式按照咱們預期的值輸出了,可是第二個表達式卻沒有。這裏要強調一點:第二個表達式沒有涉及到強制類型轉換。他把這個表達式當作了兩個,一個是塊{},還有一個是+1,把{}丟棄l,因此輸出的值1。至於1+{},js把他當作一個表達式,因此{}被強制轉換爲'[object object]';第三個表達式加了(),使js認爲{}+1是一個總體,因此{}也被強制轉換了。
到這裏我想說的基本就結束了。若是文中有錯誤或者有某些強制轉換的情形沒有涉及到請及時留言告知,我會修改並補充進去。