“一法三表”徹底記住JS顯式/隱式強(qiáng)制類型轉(zhuǎn)換

一、導(dǎo)讀

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

二、結(jié)論

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

表一

三表之二:各類型轉(zhuǎn)成基本類型對照表:
表二

三表之三:所有隱式類型轉(zhuǎn)換情形發(fā)生時的分析方法:

表三

三、分析

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舉例,我們知道它的原型鏈?zhǔn)窍聢D這樣的,也就是所有基本類型都能用Object.prototype的toString()和valueOf()兩個方法,拿上面let num = 12舉例,如果JS沒在Number.prototype上覆蓋定義toString()和valueOf()的話,那么num.toString()的結(jié)果就是"[object Number]"了。正是因為全部的基本類型都覆蓋了這2個方法,才有了表一五花八門的結(jié)果。


Number類型變量的原型鏈

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

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

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

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

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

2、表二的分析

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

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

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

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

3、表三的分析

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

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

(2)==、!= 這兩個寬松相等判斷。==恐怕是面試官最愛問的了??赐赀@個分析再惡心的都是手到擒來。首先結(jié)論ES規(guī)范說明==除非中途兩邊類型一致了,否則最終都要轉(zhuǎn)成數(shù)字。具體步驟是:
第1步:兩邊全部用基本法轉(zhuǎn)成基本類型;
第2步:如果兩邊依然類型不一樣,則統(tǒng)一轉(zhuǎn)成數(shù)字再比較。
注意:用基本法轉(zhuǎn)換后,不要管結(jié)果是什么,直接轉(zhuǎn)成數(shù)字再比較。
比如 經(jīng)典的[] == ![],!的優(yōu)先級高于==,所以先算![],對照表二知道結(jié)果是false,所以變成判斷 [] == false,用基本法轉(zhuǎn)換[]轉(zhuǎn)換成了“”(空字符串),變成判斷“” == false,ok要轉(zhuǎn)數(shù)字,對照表二,“”會轉(zhuǎn)成0,false也轉(zhuǎn)成數(shù)字0,所以最后 0==0 成立。

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

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

4、特殊情況分析

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

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

小伙伴們可以按照本文一法三表的方法完成下面的習(xí)題,再去瀏覽器驗證下。
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]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容