“一法三表”徹底記住JS顯式/隱式強制類型轉換

一、導讀

由於各種歷史原因javaScript的類型轉換真的令人吐血。本文是老弟翻閱各種材料自己總結出的javaScript強制類型轉換規則,整理了3張表和1個分析方法,便於記憶,小夥伴們可以先看結論,繼續往下看分析有助於理解和記憶。
前言:首先要明白類型轉換話題的範疇有多大?我們要討論的是Boolean、String、Number、Array、Function、Date、Object這麼多類型之間互相轉來轉去嗎?當然不是,我們的目標只有2個:基本類型轉成基本類型、複合類型轉成基本類型。new String('hello')、new Number(1024)這種用裝箱操作來把基本類型轉成複合類型就不屬於討論的範疇(裝箱甚至不能算是類型轉換),再比如你讓數字轉成函數、數組轉成函數本身就沒意義。所以我們的目標就是基本類型轉成基本類型、複合類型轉成基本類型

二、結論

一法三表,指的是記住三張表格裏的特殊轉換,其他情況全都可以用一法分析出來。
一法:基本法。
基本法是我按照ES5規範的ToPrimitive方案抽離出來的叫法。步驟是:任何類型(基本類型和複合類型)做轉換時先檢查自己是否有valueOf()方法,如果有並且返回基本類型值,就使用該值做強制類型轉換,如果valueOf()返回的還是複合類型,那就放棄轉去調用toString()方法,把toString()返回的值做強制類型轉換。如果valueOf()和toString()都不返回基本類型(或不存在)就做不了類型轉換並且報TypeError錯。
三表之一:各類型toString()和valueOf()方法返回值對照表:


三表之二:各類型轉成基本類型對照表:

三表之三:所有隱式類型轉換情形發生時的分析方法:

三、分析

1、表一的分析。valueOf()和toString()的作用很重要

基本法就是完全依賴這2個方法的。
(1)JS在Boolean.prototype、String.prototype、Number.prototype、Symbol.prototype、Function.prototype、Date.prototype、Array.prototype、Object.prototype上都分別定義了各自的valueOf()和toString()方法。

(2)拿上面let num = 12舉例,我們知道它的原型鏈是下圖這樣的,也就是所有基本類型都能用Object.prototype的toString()和valueOf()兩個方法,拿上面let num = 12舉例,如果JS沒在Number.prototype上覆蓋定義toString()和valueOf()的話,那麼num.toString()的結果就是"[object Number]"了。正是因爲全部的基本類型都覆蓋了這2個方法,纔有了表一五花八門的結果。


(3)可以看到,除了Date對象的valueOf返回了當前時間到1970-1-1日的毫秒數(跟date.getTime()的效果一樣)以外,其他8個全部和Object的效果一致——返回對象本身,其中Srting、Number、Boolean、Symbol這4個本身就是基本類型。注意這裏的valueOf全都是對象各自覆蓋後了的,而不是通過原型鏈找到的Object.valueOf(),儘管返回的結果是一樣的。

(4)相反,所有的對象不僅覆蓋了toString方法,還徹底改變了返回值,Array對象就是所有元素調用自己的toString再拼接起來;Date對象就是調用toDateString()和toTimeString()再拼接起來;其他都是直接加個引號。

(5)let num = 12和let num = new Number(12),雖然兩個不相等,但是他們的toString和valueOf返回值是一樣的。因爲new Number(12)起到的作用是裝箱,它的核心還是12這個數字。

(6)let num = 12,num.toString()和Object.prototype.toString().call( num )肯定是不一樣的。

(7)因爲valueOf()返回的都是對象本身,所以開發者直接取對象變量就好了,基本不會去調用valueOf(),基本都是自動被引擎調用。

2、表二的分析

(1)首先必須清楚的是表二里“轉Number”、“轉String”和“轉Boolean”指的是採用Number()、String()、Boolean()這三個全局函數顯式轉換的結果。

(2)先看轉String那一列,會發現除了null、undefined兩個,其他類型的顯式轉換全部可以按照基本法轉換得到(調用自己toString()得出的結果),這說明了顯式類型轉換和隱式轉換的結果保持了一致,這個肯定得一致,要不然程序員得瘋掉。null和undefined有點特殊,用String(null)會得到“null”字符串。

(3)再看轉Number那一列,會發現所有的複合類型轉成Number也可以按照基本法轉換得到,所以複合類型要轉成數字,都是一律先轉成字符串,字符串再轉成數字。比如[1]要轉成數字,按照基本法首先看valueOf()返回數組對象本身,數組不是基本類型,所以調用toString()得到字符串“1”,“1”再轉成數字是1。所以剩下的只要記住這一列null、undefined、Boolean和String類型這4種轉成Number的結果就很輕鬆了。

(4)再看轉Boolean那一列,轉Boolean很好記憶,記住只有false和undefined、null、+0、-0、NaN、"",這7個值會轉成false,其他一律轉成true。

3、表三的分析

表一表二列舉出的是每種類型轉成基本類型的結果。表三就是爲了說明將要發生類型轉換時JS時怎麼轉的,"1" == 1 類型不一致肯定要轉成一樣的再比較,那麼會把“1”轉成數字呢?還是把1轉成字符串呢?還是誰在==左邊就轉成對方的類型呢?

(1)單元+法、減乘除、取餘、自增、自減這些只適用於數學計算,所以一律會轉成數字。比如1-{age:30},{age:30}用基本法轉成“[object Object]”再轉成數字得到NaN,NaN+1還是NaN。

(2)==、!= 這兩個寬鬆相等判斷。==恐怕是面試官最愛問的了。看完這個分析再噁心的都是手到擒來。首先結論ES規範說明==除非中途兩邊類型一致了,否則最終都要轉成數字。具體步驟是:
第1步:兩邊全部用基本法轉成基本類型;
第2步:如果兩邊依然類型不一樣,則統一轉成數字再比較。
注意:用基本法轉換後,不要管結果是什麼,直接轉成數字再比較。
比如 經典的[] == ![],!的優先級高於==,所以先算![],對照表二知道結果是false,所以變成判斷 [] == false,用基本法轉換[]轉換成了“”(空字符串),變成判斷“” == false,ok要轉數字,對照表二,“”會轉成0,false也轉成數字0,所以最後 0==0 成立。

(3)雙元+的情形要拎出來做典型,因爲它可以用於數學加法和字符串拼接,比如1+2、“1”+“2”。所以雙元+要看+號兩邊元素的類型。具體步驟是:
第1步:+號兩邊類型不一致,先用基本法轉成基本類型;
第2步:如果+號兩邊的基本類型有一個是字符串,就把另一個轉成字符串再拼接;
第3步:如果+號兩邊的基本類型都不是字符串,就都轉成數字再相加。
比如1+{age:30},{age:30}用基本法轉成“[object Object]”是字符串,所以把1轉成“1”再拼接起來等於“1[object Object]”;再比如true+false,用基本法轉一下,都得到自己,+號兩邊都是Boolean不是字符串,所以兩邊都轉成數字,true轉成1,false轉成0,相加得到1。

(4)轉Boolean類型的就對照表二就可以了。

4、特殊情況分析

(1)Date類型遇到雙元+時。例如:let date = new Date(); let res = date + 1;實際的結果是輸出"Fri Mar 20 2020 20:14:10 GMT+0800 (中國標準時間)1"。按道理這裏date應該用基本法調用先調用valueOf()轉換成基本類型1584670735713再做加法,但是沒有,而是調用了toString()轉換成了字符串再做拼接。
(2)Object.create(null)創建的對象做轉換時。例如:let res = Object.create(null) + "hello"會報錯Uncaught TypeError: Cannot convert object to primitive value。是因爲用Object.create(null)創建的對象的原型是null,所以沒有valueOf()和toString()方法,沒法做類型轉換。
(3)稀疏數組,即數組有空值時。看下面這個例子
var arr = [0]; arr[1] = undefined; arr[2] = null; arr[4]=4; 創建的數組是這樣的[0,undefined,null,空值,3]。
arr.toString(); // "0,,,,4"
String(arr); // "0,,,,4"
String(arr[1]); // "undefined"
String(arr[2]); // "null"
String(arr[3]); // "undefined"
注意到,如果是直接調用數組的toString()或者用全局String()顯式轉換時,JS會把undefined、null、空值轉成""空字符串參與拼接;而如果單獨摳出來用String()做顯式轉換的話卻會變成"undefined"、"null"和"undefined"。這還是值得注意的。

四、習題(做完不再怕面試)

小夥伴們可以按照本文一法三表的方法完成下面的習題,再去瀏覽器驗證下。
1、[] + {}
2、{} + new Date()
3、[] - new Date()
4、{} + true
5、{} - false
6、[] + ""
7、[] + true + "2" + 3 - "99"
8 、null - undefined
8、null == false
9、null == undefined
10、undefined == false
11、true == "45"
12、false == 45
13、 "" == 0
14、 "" == false
15、0 == []
16、0 == {}
17、"0" == 0
18、"0" == false
19、"0" == []
20、[] == ![]
21、"" == [null]
22、2 == [2]
23、2 == ["2"]
24、1 == [true]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章