你須要知道的JavaScript隱式類型轉換

前言

JavaScript做爲一門弱類型語言,其變量能夠任意賦值從而轉換類型,這既是優勢也有缺點。若是開發者明白本身的賦值或操做會引起類型轉換,那麼也就無所謂了,但不少狀況下開發者是不清楚本身的操做可能引起隱式類型轉換,這個就有點危險,並且還很差問題定位時麻煩多多。 好比下面這張來源於Here的圖,相信不少人都會感謝Brendan Eich(布蘭登·艾克),哈哈。javascript

笑過以後咱們開始揭開JavaScript隱式轉換的一角,先了解一些預備知識,而後對這些題目解疑,最後進行一些拓展html

預備知識

==弱相等運算符

規範文檔Abstract Equality Comparison的翻譯過來就是下面的:java

  1. 若是x非正常值(好比x自己會拋出錯誤),則中斷執行
  2. 若是y非正常值(同上),則中斷執行
  3. 若是x的數據類型和y的數據類型相同,則返回以嚴格運算符執行判斷的結果,即x===y的結果
  4. 若是xnullyundefined,返回true
  5. 若是xundefinedynull,返回true
  6. 若是x的數據類型是Numbery的數據類型是String,則將y轉成Number,而後返回x==toNumber(y)的結果
  7. 若是x的數據類型是Stringy的數據類型是Number,則將x轉成Number,而後返回toNumber(x)==y的結果
  8. 若是x的數據類型是Boolean,則將x轉成Number,而後返回toNumber(x)==y的結果
  9. 若是y的數據類型是Boolean,則將y轉成Number,而後返回x==toNumber(y)的結果
  10. 若是x的數據類型是StringNumber或者Symboly的數據類型是Object,則將y轉成原始類型,而後返回x==toPrimitive(y)的結果
  11. 若是x的數據類型是Objecty的數據類型是StringNumber或者Symbol,則將x轉成原始類型,而後返回toPrimitive(x)==y的結果
  12. 返回false

ToNumber

規範文檔ToNumber翻譯過來就是:數組

參數類型 結果
完成標誌( 例如returnbreakthrow等) 若是參數是一個異常中斷,就返回這個參數,不然就返回該參數轉換成Number以後的數值
Undefined 返回NaN
Null 返回+0
Boolean 若是參數是true,返回1;若是參數是false,返回+0
Number 返回參數(不作轉換)
String StringToNumber
Symbol 拋出一個TypeError異常
Object 採用下述的步驟:
1.利用ToPrimitive(argument,hint Number)的方式轉成原始類型
2.將上述步驟的原始類型轉成數值,即ToNumber(primValue),並返回該數值

StringToNumber

規範文檔ToNumber Applied to the String Type裏面東西有點多,感興趣可自行跳轉查閱,這裏簡單的說一下:瀏覽器

  1. 若是字符串中只包含數字(包括前面帶加號或負號的狀況),則將其轉換爲十進制數值,即"1"會變成1"123"會變成123,而"011"會變成11(注意:前導的零被忽略了);
  2. 若是字符串中包含有效的浮點格式,如"1.1",則將其轉換爲對應的浮點數值(一樣,也會忽略前導零);
  3. 若是字符串中包含有效的十六進制格式,例如"0xf",則將其轉換爲相同大小的十進制整數值;
  4. 若是字符串是空的(不包含任何字符),則將其轉換爲0
  5. 若是字符串中包含除上述格式以外的字符,則將其轉換爲NaN

ToPrimitive

規範文檔ToPrimitive。東西比較多,簡單概述就是。ToPrimitive(input[, preferredType])函數接受兩個參數,第一個input爲被轉換的數據,第二個preferredType爲但願轉換成的類型(默認爲default,接受的值爲NumberString)。若是input不是Oject,即基礎類型(Null, Undefinded, String, Boolean, Number,Symbol, 以及未來歸入規範的BigInt)時,直接返回輸入input。若是inputObject時,在第二個參數爲空的狀況下,而且inputDate的實例時,此時preferredType會被設置爲String,其餘爲空的狀況preferredType默認爲Number安全

若是preferredTypeNumberToPrimitive執行過程以下:app

  1. 若是obj爲原始值,直接返回;
  2. 不然調用obj.valueOf(),若是執行結果是原始值,返回之;
  3. 不然調用obj.toString(),若是執行結果是原始值,返回之;
  4. 不然拋異常。

若是preferredType爲String,將上面的第2步和第3步調換,即:函數

  1. 若是obj爲原始值,直接返回;
  2. 不然調用obj.toString(),若是執行結果是原始值,返回之;
  3. 不然調用obj.valueOf(),若是執行結果是原始值,返回之;
  4. 不然拋異常。

ToBoolean

規範文檔ToBoolean,概述一下就是:ui

  1. undefinednull直接返回false
  2. SymbolObject永遠返回true
  3. 自己就是Boolean型直接返回本來值
  4. Number型的除+0-0NaN返回false之外,其他都返回true
  5. String型中空字符串(即長度爲0)返回false,其餘狀況返回true

解疑

1. typeof NaN //"number"

NaN,即Not a Number,不是一個數字,它自己是Number類型的,因此利用typeof判斷類型是天然返回"number"。可是NaN又是一個特殊的Number類型,它永遠爲假,同時自身也不等於自身。因此有時候在判斷一個變量是否是NaN時,能夠經過是否等於自身來判斷。spa

function isNaN(value) {
    return !value == value;
}

isNaN(NaN); //true
複製代碼

2. 9999999999999999  //10000000000000000

JavaScript針對數值,只有Number類型,採用64bits雙浮點精度,以下圖所示。

  • 1位:符號位,0表示正數,1表示負數
  • 2位到第12位(共11位):指數部分,0~2047
  • 13位到第64位(共52位):小數部分(即有效數字)

由於有效位數只有53位,因此JavaScript能精確表示的最大整數就是Math.pow(2, 53),十進制就是9007199254740992,在JavaScript也設置了Number.MAX_SAFE_INTEGER(最大安全整數)和Number.MIN_SAFE_INTEGER(最小安全整數)。當JavaScript在存儲超過9007199254740992的數值時,可能會存在精度丟失的狀況(超過52位的會被自動去掉)。例如:

如下爲摘錄阮一峯的文章:

  • 符號位決定了一個數的正負,指數部分決定了數值的大小,小數部分決定了數值的精度。
  • 指數部分一共有11個二進制位,所以大小範圍就是02047IEEE 754規定,若是指數部分的值在02047之間(不含兩個端點),那麼有效數字的第一位默認老是1,不保存在64位浮點數之中。也就是說,有效數字這時老是1.xx...xx的形式,其中xx..xx的部分保存在64位浮點數之中,最長可能爲52位。所以,JavaScript 提供的有效數字最長爲53個二進制位。***(-1)^符號位 * 1.xx...xx * 2^指數部分***
  • 上面公式是正常狀況下(指數部分在02047之間),一個數在 JavaScript 內部實際的表示形式。
  • 精度最多隻能到53個二進制位,這意味着,絕對值小於等於253次方的整數,即-253253,均可以精確表示。

3. 0.1+0.2 == 0.3 //false

一樣的這也是由於JavaScript數值精度丟失的緣由致使等於判斷爲falseJavaScript自己也考慮到了這一點,因此設置一個可接受的偏差範圍,即Number.EPSILON,當兩個值的差值小於等於這個可接受偏差範圍時,就能夠認爲這兩個數值時相等的。

function isEqual(num1, num2) {
    return Math.abs(num1-num2) <= Number.EPSILON;
}

isEqual(0.1 + 0.2, 0.3); //true
複製代碼

4. Math.min()和Math.max()

查看規範文檔可知,當Math.min()方法無參數時返回Infinity,而Math.max()無參數時返回-Infinity

JavaScript可以表示的最大數值和最小數值分別爲Number.MAX_VALUENumber.MIN_VALUE中,在大多數瀏覽器中,它們分別是1.7976931348623157e+3085e-324。能夠看到,它們都是正數,是絕對值中的最大和最小數值。

還有比他們更大或者更小的值,當計算結果獲得一個超過數值範圍的值,那麼就會轉成Ifinity(正無窮)和-Infinity(負無窮)

JavaScript也提供了2個屬性保存這兩個無窮值,分別是Number.NEGATIVE_INFINITY(負無窮)和Number.POSITIVE_INFINITY(正無窮)

5. []+[][]+{}

下面的解釋引自《JavaScript高級程序設計第三版》:

在使用一元操做符(如+、-、++、--)時,JavaScript存在隱式類型轉換。

  • 在應用於一個包含有效數字字符的字符串時,先將其轉換爲數字值
  • 在應用於一個不包含有效數字字符的字符串時,將變量的值設置爲NaN
  • 在應用於布爾值falsetrue是,分別轉換爲01
  • 在應用於對象時,先調用對象的valueOf()方法,取的一個可供操做的值。若是結果爲NaN,就在調用toString()方式轉成字符串,在執行前面的操做。

上述表述就是ToPrimitive的另外一種翻譯。未被重定義的狀況下valueOf() 方法返回指定對象的基礎類型值;toString() 方法返回一個表示該對象的字符串。

在這裏[]{}都是引用類型,是對象,因此先調用valueOf方法,後面調用toString方法。

第一個[]+[]Array對象重寫了ObjectvalueOf方法和toString方法,valueOf返回數組自己,toString返回與沒有參數(默認爲逗號拼接)的 join() 方法返回的字符串相同。因此這裏先返回了數組自己,沒法進行+操做,在調用toString方法,變成了空字符串,兩個空字符串相加,因此最後輸出空字符串。

第二個[]+{}[]最終轉成空字符串,+運算實際上變成了字符串拼接方法,因而{}調用Object的原生toString方法,轉成了「[object Object]」,最終拼接爲了「[object Object]」

6. {} + []

這個和[]+[][]+{}點類似,若是按照第五項中的解釋去理解,是得不到結果的。爲何呢?這要說到JavaScript引擎自己解釋代碼的問題了,在JavaScript解釋{}時,有兩種狀況,一種是語句塊,一種是對象定義。

當直接在控制檯輸入{}+[]時,此時解釋器將{}解釋爲語句塊,即{};+[],因此輸出就變成了+[]的結果,這裏+符號會強制轉換,執行toNumber()操做,空數組返回數字0

若是在外面加上括號,即({}+[]),那麼{}就會被解釋爲對象,最後返回「[object Object]」

7. true+true+true === 3

有運算操做符時,Boolean類型false轉爲0true轉爲1,因此左側結果爲3,值相等,類型也相等,故返回true

8. true - true

和上面一樣的理由,轉變成數值運算1-1,因此返回0

9. true == 1

相等操做符,兩側會進行toNumber操做,進行值判斷,不進行類型判斷。因此1 == 1,返回true

10. true === 1

全等操做符,既判斷值,也判斷類型,即不作類型轉換,這裏值雖然相同,一個爲Boolean類型,一個爲Number類型,因此返回false

11. (! + [] + [] + ![]).length

運算符具備優先級

這裏能夠看到邏輯非!操做符優先級比+高,因此第一個邏輯非!先執行,至關於!(+[])+[]進行toNumber操做返回0,而後邏輯非進行toBoolean操做返回true,而後![]執行,因而[]先進行toBoolean的操做,返回true,而後邏輯非操做變成false,而後執行從左至右+運算,即true+[]+false,變成了字符串拼接,因而返回「truefalse」,這個字符串的長度也就是9

12. 9 + "1"

不管是9+"1",仍是"1"+9,結果都是91+運算符能夠是數字相加運算,也能夠是字符拼接運算。可是規範文檔規定了,若是+運算符兩側存在字符串時,就調用toString()方法,進行字符串拼接操做,因此這裏結果都是91

13. 9 - "1"

-運算符和+運算符不一樣,由於-運算符就是數字運算減的操做,因此先轉成Number類型,因此不管是‘9’ -1仍是9 -‘1’,結果都是8

14. [] == 0

根據==弱相等運算符中的規則,空數組最後會執行toNumber轉成數字0,因此返回true

拓展

相信經過上面的解疑,應該掌握了大部分技巧,如今再來檢驗一波掌握的如何。

/* 猜猜下面的輸出 */

// 第一題
'true' == true

// 第二題
0 == null
複製代碼

先思考一波

.

開始思考

.

思考ing

.

完成思考


就認爲你們都思考一波了

1. 'true' == true

嘿嘿,確定有看錯而後答錯的。

不先說答案,按照流程走一波:

  1. 在這個相等運算中,左側'true'的數據類型是String,右側true的數據類型是Boolean
  2. 首先知足==弱相等運算符9條,因此布爾值true轉成數值1,返回'true'==1的值
  3. 其次'true'==1又知足第7條,因此字符串true根據上面講的規則,轉換成NaN,故返回NaN==1
  4. 而後NaN都不等於任何值,包括它自己,即NaN==NaN返回false
  5. 最後'true'==true返回false

2. 0 == null

在這個相等運算中,左側0的數據類型是Number,右側null的數據類型是Null(內部Type運算的結果,與typeof運算符無關),因此根據上面的規則,前面11條都不知足,直到第12步才返回false

另附上一份圖,自行按照流程走便可獲得答案。

參考

  1. JS中的假值
  2. JS中相等(==)運算符詳解
  3. JavaScript的精度丟失和隱式類型轉換
相關文章
相關標籤/搜索