本章旨在全面介紹強(qiáng)制類型轉(zhuǎn)換的優(yōu)缺點(diǎn)。
1.值類型轉(zhuǎn)換
??????將值從一種類型轉(zhuǎn)換為另一種類型通常稱為類型轉(zhuǎn)換,這是顯式的情況;隱式的情況稱為強(qiáng)制類型轉(zhuǎn)換。
??????JavaScript中的強(qiáng)制類型轉(zhuǎn)換總是返回標(biāo)量基本類型值,不會(huì)返回對象和函數(shù)。
??????可以這樣分別稱呼:顯式強(qiáng)制類型轉(zhuǎn)換、隱式強(qiáng)制類型轉(zhuǎn)換。(這里的顯/隱區(qū)分不是官方定的,而是根據(jù)一般的開發(fā)人員的感受而做的區(qū)分)
2.抽象值操作
??????本節(jié)介紹各種基本類型之間轉(zhuǎn)換的規(guī)則,主要介紹toString、toNumber、toBoolean,并捎帶講下toPrimitive。
2.1 toString
??????null -> 'null'
??????undefined -> 'undefined'
??????true -> 'true'
??????數(shù)字的轉(zhuǎn)換遵循通用規(guī)則,其中極大和極小的數(shù)字使用指數(shù)形式。
??????對普通對象,則調(diào)用對象的toString(Object.prototype.toString()),返回內(nèi)部屬性[[Class]]的值,這個(gè)方法也可以自己定義。
??????數(shù)組的toString就是Array類里重新定義過的,跟Object.prototype.toString()不一樣。
下面講下JSON.stringify
??????對大多數(shù)基本類型值來說,JSON.stringify和toString的效果基本相同。
??????安全的JSON值指的是能夠呈現(xiàn)為有效JSON格式的值。
??????那么什么是不安全的JSON值? undefined、function、symbol和包含循環(huán)引用的對象 都不是安全的JSON值。
??????JSON.stringify()在對象中遇到undefined、function和symbol時(shí)會(huì)自動(dòng)將其忽略,在數(shù)組中則會(huì)返回null(以保證單元位置不變)。而對包含循環(huán)引用的對象執(zhí)行JSON.stringify()會(huì)出錯(cuò)。
??????如果要對含有非法JSON值的對象做字符串化,就需要定義toJSON()方法來返回一個(gè)安全的JSON值。這個(gè)toJSON方法應(yīng)該返回一個(gè)“能夠被字符串化的安全的JSON值”。
JSON.stringify的使用小妙招:
??????JSON.stringify(..)接收一個(gè)可選參數(shù)replacer,它可以是數(shù)組或者函數(shù),用來指定對象序列化過程中哪些屬性應(yīng)該被處理,哪些應(yīng)該被排除。如果replacer是一個(gè)數(shù)組,那么它必須是一個(gè)字符串?dāng)?shù)組,其中包含序列化要處理的對象的屬性名稱,除此之外其他的屬性則被忽略;如果replacer是一個(gè)函數(shù),它會(huì)對對象本身調(diào)用一次,然后對對象中的每個(gè)屬性各調(diào)用一次,每次傳遞兩個(gè)參數(shù),鍵和值。如果要忽略某個(gè)鍵就返回undefined,否則返回指定的值。
??????JSON.stringify(..)的字符串化過程是遞歸的,遞歸的過程中會(huì)多次調(diào)用replacer函數(shù)(如果有的話),可以自己試著打印一下看看遞歸的順序。
??????JSON.stringify還有一個(gè)可選參數(shù)space,用來指定輸出的縮進(jìn)格式。space為正整數(shù)時(shí)是指定每一級(jí)縮進(jìn)的字符數(shù),它還可以是字符串,此時(shí)最前面的十個(gè)字符被用于每一級(jí)的縮進(jìn)。(后半句話沒懂是什么意思。)
??????mdn對space是這么解釋的:
??????A String or Number object that's used to insert white space into the output JSON string for readability purposes.
??????If this is a Number, it indicates the number of space characters to use as white space; this number is capped at 10 (if it is greater, the value is just 10). Values less than 1 indicate that no space should be used.
??????If this is a String, the string (or the first 10 characters of the string, if it's longer than that) is used as white space. If this parameter is not provided (or is null), no white space is used.
??????我覺得比書上的中文說得清楚。
2.2 toNumber
??????true -> 1
??????false -> 0
??????undefined -> NaN
??????null -> 0
??????ToNumber對字符串的處理基本遵循數(shù)字常量的相關(guān)規(guī)則。處理失敗時(shí)返回NaN。不同之處是ToNumber對以0開頭的十六進(jìn)制數(shù)并不按十六進(jìn)制處理(而是按十進(jìn)制,參見第2章)。
??????(↑這句沒看懂,Number('0x011')也成功轉(zhuǎn)成了17,為什么作者要說“并不按十六進(jìn)制處理呢?)
??????對象(包括數(shù)組)會(huì)首先被轉(zhuǎn)換為相應(yīng)的基本類型值,如果返回的是非數(shù)字的基本類型值,則再遵循以上規(guī)則將其強(qiáng)制轉(zhuǎn)換為數(shù)字。
??????對象轉(zhuǎn)換為基本類型值的過程:首先檢查該值是否有valueOf()方法,如果有并且返回基本類型值,就使用該值進(jìn)行強(qiáng)制類型轉(zhuǎn)換;如果沒有就使用toString()的返回值(如果存在)來進(jìn)行強(qiáng)制類型轉(zhuǎn)換。
2.3 toBoolean
??????JavaScript規(guī)范具體定義了一小撮可以被強(qiáng)制類型轉(zhuǎn)換為false的值:undefined、null、false、+0、-0、NaN、空字符串。
??????我們可以理解為假值列表以外的值都是真值。
??????(ie瀏覽器有個(gè)神妙的假值對象document.all,它為什么會(huì)是個(gè)假值的原因,反正是歷史原因。)
3.顯式強(qiáng)制類型轉(zhuǎn)換
??????顯式強(qiáng)制類型轉(zhuǎn)換是那些顯而易見的類型轉(zhuǎn)換。我們在編碼時(shí)應(yīng)盡可能地將類型轉(zhuǎn)換表達(dá)清楚,以免給別人留坑。類型轉(zhuǎn)換越清晰,代碼可讀性越高,更容易理解。
3.1 字符串和數(shù)字之間的顯式轉(zhuǎn)換
??????tips: JavaScript有一處奇特的語法,即構(gòu)造函數(shù)沒有參數(shù)時(shí)可以不用帶()
??????涉及字位運(yùn)算符的強(qiáng)制轉(zhuǎn)換
??????前面提過,字位運(yùn)算符只適用于32位整數(shù),所以運(yùn)算符會(huì)強(qiáng)制操作數(shù)使用32位格式。這是通過抽象操作ToInt32來實(shí)現(xiàn)的,比如"123"會(huì)先被ToNumber轉(zhuǎn)換為123,然后再執(zhí)行ToInt32。
??????雖然嚴(yán)格說來并非強(qiáng)制類型轉(zhuǎn)換(因?yàn)榉祷刂殿愋筒]有發(fā)生變化),但字位運(yùn)算符(如|和~)和某些特殊數(shù)字一起使用時(shí)會(huì)產(chǎn)生類似強(qiáng)制類型轉(zhuǎn)換的效果,返回另外一個(gè)數(shù)字。
??????例:
??????0 | -0 // 0
??????0 | NaN // 0
??????0 | Infinity // 0
??????0 | -Infinity // 0
??????以上這些特殊數(shù)字無法以32位格式呈現(xiàn)(因?yàn)樗鼈儊碜?4位IEEE 754標(biāo)準(zhǔn),參見第2章),因此ToInt32返回0。
??????然后作者說到~的用法
??????很多語言中會(huì)有“哨位值”,用來表示特殊的含義,比如JavaScript中的indexOf()用-1表示沒有搜索到指定子串。對于這樣的方法如果我們直接用>=0或者===-1這樣的判斷,就是把方法的實(shí)現(xiàn)細(xì)節(jié)暴露出來了,而用~計(jì)算indexOf()的結(jié)果,剛好~-1為0,是假值,其它結(jié)果是真值。
??????作者認(rèn)為if (~a.indexOf(..)這樣的判斷比>=0或者===-1更簡潔。
??????然后作者開始討論~~
??????~~中的第一個(gè)~執(zhí)行ToInt32并反轉(zhuǎn)字位,然后第二個(gè)~再進(jìn)行一次字位反轉(zhuǎn),即將所有字位反轉(zhuǎn)回原值,最后得到的仍然是ToInt32的結(jié)果(只適用于32位數(shù)字)。
??????~~x能將值截除為一個(gè)32位整數(shù),但它對負(fù)數(shù)的處理與Math. floor(..)不同。
??????Math.floor(-49.6) // -50
??????~~-49.6 // 49
3.2 顯式解析數(shù)字字符串
??????強(qiáng)制轉(zhuǎn)換方法Number() 和 解析方法parseInt()、parseFloat()的區(qū)別:解析允許字符串中含有非數(shù)字字符,解析按從左到右的順序,如果遇到非數(shù)字字符就停止。而轉(zhuǎn)換不允許出現(xiàn)非數(shù)字字符,否則會(huì)失敗并返回NaN。
??????parseInt(..)針對的是字符串值,非字符串參數(shù)會(huì)首先被強(qiáng)制類型轉(zhuǎn)換為字符串。依賴這樣的隱式強(qiáng)制類型轉(zhuǎn)換并非上策,應(yīng)該避免向parseInt(..)傳遞非字符串參數(shù)。
??????parseInt(..)第二個(gè)參數(shù)可以指定轉(zhuǎn)換的基數(shù)。如果沒有傳第二個(gè)參數(shù),ES5前parseInt(..)會(huì)根據(jù)字符串的第一個(gè)字符來自行決定基數(shù),從ES5開始parseInt(..)則默認(rèn)轉(zhuǎn)換為十進(jìn)制數(shù)(除非0x開頭)。
??????如果使用不當(dāng),parseInt會(huì)出現(xiàn)一些難以理解的結(jié)果,但其實(shí)并沒毛病。
??????比如:parseInt(1/0, 19),實(shí)際上是parseInt("Infinity", 19)。第一個(gè)字符是"I",以19為基數(shù)時(shí)值為18。所以最后的結(jié)果是18,而非Infinity或者報(bào)錯(cuò)。
??????還有一些例子:
??????parseInt(0.000008) // 0 (0來自于"0.000008")
??????parseInt(0.0000008) // 8 (8來自于"8e-7")
??????parseInt(false, 16) // 250 ("fa"來自于"false")
??????parseInt(parseInt, 16) // 15 ("f"來自于"function..")
??????parseInt("0x10") // 16
??????parseInt("103", 2) // 2
3.3 顯式轉(zhuǎn)換為布爾值
??????Boolean(..)是顯式的ToBoolean強(qiáng)制類型轉(zhuǎn)換,還有種寫法是!!
4.隱式強(qiáng)制類型轉(zhuǎn)換
4.1 隱式的簡化
??????隱式強(qiáng)制類型轉(zhuǎn)換 相當(dāng)于 省略了一些轉(zhuǎn)換的代碼,讓轉(zhuǎn)換的環(huán)節(jié)看起來變少了,一些中間環(huán)節(jié)被隱藏掉了,代碼看起來更簡潔。
4.2 字符串和數(shù)字之間的隱式強(qiáng)制類型轉(zhuǎn)換
??????前面講過+運(yùn)算符可以把字符串轉(zhuǎn)成數(shù)字,然后可以進(jìn)行數(shù)字加法,+運(yùn)算符也可以用于字符串拼接。那么JavaScript怎么決定最后執(zhí)行什么操作?
??????舉幾個(gè)例子:
??????'42' + '0' // 420
??????42 + 0 // 42
??????[1, 2] + [3, 4] // 1,23,4
??????ES5規(guī)定,如果某個(gè)操作數(shù)是字符串的話,+將進(jìn)行拼接操作,否則執(zhí)行數(shù)字加法。
如果操作數(shù)是對象,它會(huì)先被調(diào)用valueOf,獲取值,如果valueOf得不到基本類型值,就會(huì)調(diào)用它的toString。
??????再說個(gè)神奇的例子:
??????[] + {} // [object Object]
??????{} + [] // 0
??????后面再分析
??????然后討論從字符串強(qiáng)制轉(zhuǎn)換為數(shù)字的情況
??????比如說用-運(yùn)算符:
??????[3] - [2] // 1
??????[3, 4] - [2] // NaN
??????除了-運(yùn)算符,*和/也會(huì)將操作數(shù)強(qiáng)制轉(zhuǎn)換為數(shù)字。
??????我自己在瀏覽器控制臺(tái)試了下
??????{} - 1 // -1
??????[] - 1 // -1
??????唉,也不知道作何解釋。
4.3 布爾值到數(shù)字的隱式強(qiáng)制類型轉(zhuǎn)換
??????可以對布爾值用數(shù)學(xué)運(yùn)算符,它會(huì)被隱式轉(zhuǎn)換成數(shù)字0或1。有的時(shí)候這樣是有用的。
4.4 隱式強(qiáng)制類型轉(zhuǎn)換為布爾值
??????會(huì)發(fā)生布爾值隱式強(qiáng)制類型轉(zhuǎn)換的情況:
??????(1) if (..)語句中的條件判斷表達(dá)式
??????(2) for ( .. ; .. ; .. )語句中的條件判斷表達(dá)式
??????(3) while (..)和do..while(..)循環(huán)中的條件判斷表達(dá)式。
??????(4) ? :三目運(yùn)算中的條件判斷表達(dá)式
??????(5) 邏輯運(yùn)算符||(邏輯或)和&&(邏輯與)左邊的操作數(shù)(作為條件判斷表達(dá)式)
4.5 ||和&&
??????在JavaScript中||和&&返回的并不是布爾值,而是兩個(gè)操作數(shù)中的一個(gè)(且僅一個(gè))。
??????||和&&首先會(huì)對第一個(gè)操作數(shù)(a和c)執(zhí)行條件判斷,第一個(gè)操作數(shù)如果不是布爾值,會(huì)被強(qiáng)制類型轉(zhuǎn)換成布爾值。
??????對于||來說,如果條件判斷結(jié)果為true就返回第一個(gè)操作數(shù)的值,如果為false就返回第二個(gè)操作數(shù)的值。&&則相反。
??????a || b 大致相當(dāng)于 a ? a : b
??????a && b 大致相當(dāng)于 a ? b : a
??????之所以說大致相當(dāng),是因?yàn)槿绻鸻是一個(gè)復(fù)雜一些的表達(dá)式,用?:的寫法它可能被執(zhí)行兩次,而在a || b和a && b中a只執(zhí)行一次。
??????||常用來設(shè)默認(rèn)值。
??????&&常被代碼壓縮工具用來壓縮代碼,如if (a) { foo(); }會(huì)被壓縮成a && foo()。
4.6 符號(hào)的強(qiáng)制類型轉(zhuǎn)換
??????symbol不能被強(qiáng)制類型轉(zhuǎn)換為數(shù)字(顯式隱式都不行)
??????symbol可以被強(qiáng)制類型轉(zhuǎn)換為布爾值(顯式和隱式結(jié)果都是true)
??????symbol可以被顯式強(qiáng)制類型轉(zhuǎn)換為字符串,但不能隱式轉(zhuǎn)換成字符串。
??????例:
var s1 = Symbol('cool')
String(s1) // 'Symbol(cool)'
s1 + '' // 會(huì)報(bào)錯(cuò) Uncaught TypeError: Cannot convert a Symbol value to a string
5.寬松相等==和嚴(yán)格相等===
??????==允許在相等比較中進(jìn)行強(qiáng)制類型轉(zhuǎn)換,而===不允許。
5.1 兩者的性能
??????如果比較的兩個(gè)值類型不同,==會(huì)先進(jìn)行強(qiáng)制類型轉(zhuǎn)換。如果兩個(gè)值類型相同,則==和===使用相同的算法。
??????性能上兩者幾乎沒有差別,使用時(shí),只需要考慮有沒有強(qiáng)制類型轉(zhuǎn)換的必要,有就用==,沒有就用===,不用在乎性能。
5.2 抽象相等
??????es5規(guī)范規(guī)定了“抽象相等比較算法”定義了==運(yùn)算符的行為。
??????它規(guī)定:
??????(1)如果兩個(gè)值的類型相同,就僅比較它們是否相等。(注意特例:NaN不等于NaN;+0等于-0)
??????(2)兩個(gè)對象之間比較,如果兩個(gè)對象指向同一個(gè)值時(shí)即視為相等。
??????(3)兩個(gè)值類型不相同,會(huì)發(fā)生隱式強(qiáng)制類型轉(zhuǎn)換,強(qiáng)制類型轉(zhuǎn)換可能會(huì)發(fā)生在其中一個(gè)值,也可能兩個(gè)都被轉(zhuǎn)換,之后再被進(jìn)行比較。
??????下面進(jìn)行更具體的分情況討論:
??????①字符串和數(shù)字之間的相等比較
??????前面說過,==的兩個(gè)比較值的類型不一致時(shí),會(huì)先進(jìn)行強(qiáng)制類型轉(zhuǎn)換。那么轉(zhuǎn)換哪個(gè)呢?
??????es5規(guī)范規(guī)定:如果Type(x)是數(shù)字,Type(y)是字符串,則返回x == ToNumber(y)的結(jié)果;如果Type(x)是字符串,Type(y)是數(shù)字,則返回ToNumber(x) == y的結(jié)果。
??????②其他類型和布爾類型之間的相等比較
?????? es5規(guī)范規(guī)定:如果Type(x)是布爾類型,則返回ToNumber(x) == y的結(jié)果;如果Type(y)是布爾類型,則返回x == ToNumber(y)的結(jié)果。
??????所以true、false這樣的比較值會(huì)被轉(zhuǎn)成1和0再進(jìn)行比較。
??????③null和undefined之間的相等比較
??????es5規(guī)范規(guī)定:如果x為null, y為undefined,則結(jié)果為true;如果x為undefined, y為null,則結(jié)果為true。
??????在==中null和undefined相等(它們也與其自身相等),除此之外其他值都不存在這種情況。
?????? 比方說null == ''、null == false、null == 0的判斷結(jié)果都是false
?????? 根據(jù)上述規(guī)則,在開發(fā)中如果要判斷一個(gè)值是否為null或undefined,就沒必要寫成a === null || a === undefined,直接寫成a == null就行,還更簡潔。
??????④對象和非對象之間的相等比較
??????es5規(guī)范規(guī)定:如果Type(x)是字符串或數(shù)字,Type(y)是對象,則返回x == ToPrimitive(y)的結(jié)果;如果Type(x)是對象,Type(y)是字符串或數(shù)字,則返回ToPrimitive(x) == y的結(jié)果。
??????舉例:42 == [42],對象[42]會(huì)先被調(diào)用toPrimitive,得到"42",然后"42" == 42又被強(qiáng)制類型轉(zhuǎn)換了一次變成42 == 42,判斷結(jié)果為true。
??????Number對象、String對象(平時(shí)我們不怎么顯式地去用)存在“拆封”,這個(gè)拆封的過程也會(huì)調(diào)用對象的toPrimitive。
??????舉例:"abc" === new String("abc") // false
??????"abc" == new String("abc") // true
5.3 奇葩情況集錦
??????①給對象定義了奇葩的valueOf方法,然后就能看到各種壯觀的奇葩事情。
??????②各種假值的相等比較
??????由于強(qiáng)制類型轉(zhuǎn)換的原因,所以假值之間比較可能會(huì)有一些看起來難以理解的結(jié)果,比如[] == ''為真、[] == false為真,這都是因?yàn)閺?qiáng)制類型轉(zhuǎn)換,想想Number('')為0、[].toString()為''就知道了。
??????③[] == ![]為真
??????原因是這樣的,![]首先把[]轉(zhuǎn)成布爾類型值,得true,然后前面那個(gè)!把它轉(zhuǎn)成false,然后[] == false為真,因?yàn)樗鼈兌急晦D(zhuǎn)成數(shù)字了。
??????對上述奇葩情況做總結(jié),首先不要拿布爾值做==判斷,布爾值會(huì)被轉(zhuǎn)成數(shù)字的,然后就會(huì)看起來很靈異,咱犯不上那樣,布爾值就不要拿來==了。其次,兩邊的值有[]、''或者0的,盡量不要用==,這樣就能避開幾乎所有奇奇怪怪的強(qiáng)制類型轉(zhuǎn)換行為了。
??????作者小tips:對于typeof,使用==是安全的,因?yàn)閠ypeof總是返回七個(gè)字符串之一,其中沒有空字符串。所以在類型檢查過程中不會(huì)發(fā)生隱式強(qiáng)制類型轉(zhuǎn)換,typeof x == "function"是安全的。所以代碼中按需選擇==和===即可,沒必要處處用===。
6.抽象關(guān)系比較
??????這一小節(jié)討論a < b中涉及的隱式強(qiáng)制類型轉(zhuǎn)換。
??????ES5規(guī)范定義了“抽象關(guān)系比較”(abstract relational comparison),分為兩個(gè)部分:比較雙方都是字符串和其他情況。
??????①比較雙方首先調(diào)用ToPrimitive,如果結(jié)果出現(xiàn)非字符串,就根據(jù)ToNumber規(guī)則將雙方強(qiáng)制類型轉(zhuǎn)換為數(shù)字來進(jìn)行比較。
??????②如果比較雙方都是字符串,則按字母順序來進(jìn)行比較。
??????奇怪的例子:
??????{a: 1} <= {a: 2} // true
??????{a: 1} >= {a: 2} // true
??????這是為什么呢?因?yàn)楦鶕?jù)規(guī)范,a <= b被處理為b < a,然后將結(jié)果反轉(zhuǎn)。而{a: 1}和{a: 2}的toPrimitive都是"[object Object]",a < b為false,a > b也為false。
??????我們可能以為<=應(yīng)該是“小于或者等于”,但其實(shí)在JavaScript中<=是“不大于”的意思(即!(a > b),處理為!(b < a))。同理,a >= b處理為b <= a。
??????如果要避免a < b中發(fā)生隱式強(qiáng)制類型轉(zhuǎn)換,我們只能確保a和b為相同的類型,除此之外別無他法。