本章旨在全面介紹強(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)量基本類型值投慈,不會返回對象和函數(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]]的值前普,這個方法也可以自己定義肚邢。
??????數(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ù)組中則會返回null(以保證單元位置不變)劈狐。而對包含循環(huán)引用的對象執(zhí)行JSON.stringify()會出錯罐孝。
??????如果要對含有非法JSON值的對象做字符串化,就需要定義toJSON()方法來返回一個安全的JSON值肥缔。這個toJSON方法應(yīng)該返回一個“能夠被字符串化的安全的JSON值”莲兢。
JSON.stringify的使用小妙招:
??????JSON.stringify(..)接收一個可選參數(shù)replacer,它可以是數(shù)組或者函數(shù)辫继,用來指定對象序列化過程中哪些屬性應(yīng)該被處理怒见,哪些應(yīng)該被排除。如果replacer是一個數(shù)組姑宽,那么它必須是一個字符串?dāng)?shù)組遣耍,其中包含序列化要處理的對象的屬性名稱,除此之外其他的屬性則被忽略炮车;如果replacer是一個函數(shù)舵变,它會對對象本身調(diào)用一次,然后對對象中的每個屬性各調(diào)用一次瘦穆,每次傳遞兩個參數(shù)纪隙,鍵和值。如果要忽略某個鍵就返回undefined扛或,否則返回指定的值绵咱。
??????JSON.stringify(..)的字符串化過程是遞歸的,遞歸的過程中會多次調(diào)用replacer函數(shù)(如果有的話)熙兔,可以自己試著打印一下看看遞歸的順序悲伶。
??????JSON.stringify還有一個可選參數(shù)space,用來指定輸出的縮進(jìn)格式住涉。space為正整數(shù)時是指定每一級縮進(jìn)的字符數(shù)麸锉,它還可以是字符串,此時最前面的十個字符被用于每一級的縮進(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ī)則。處理失敗時返回NaN碱屁。不同之處是ToNumber對以0開頭的十六進(jìn)制數(shù)并不按十六進(jìn)制處理(而是按十進(jìn)制磷脯,參見第2章)。
??????(↑這句沒看懂忽媒,Number('0x011')也成功轉(zhuǎn)成了17争拐,為什么作者要說“并不按十六進(jìn)制處理呢?)
??????對象(包括數(shù)組)會首先被轉(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瀏覽器有個神妙的假值對象document.all藕坯,它為什么會是個假值的原因团南,反正是歷史原因。)
3.顯式強(qiáng)制類型轉(zhuǎn)換
??????顯式強(qiáng)制類型轉(zhuǎn)換是那些顯而易見的類型轉(zhuǎn)換炼彪。我們在編碼時應(yīng)盡可能地將類型轉(zhuǎn)換表達(dá)清楚吐根,以免給別人留坑。類型轉(zhuǎn)換越清晰辐马,代碼可讀性越高拷橘,更容易理解。
3.1 字符串和數(shù)字之間的顯式轉(zhuǎn)換
??????tips: JavaScript有一處奇特的語法喜爷,即構(gòu)造函數(shù)沒有參數(shù)時可以不用帶()
??????涉及字位運(yùn)算符的強(qiáng)制轉(zhuǎn)換
??????前面提過膜楷,字位運(yùn)算符只適用于32位整數(shù),所以運(yùn)算符會強(qiáng)制操作數(shù)使用32位格式贞奋。這是通過抽象操作ToInt32來實(shí)現(xiàn)的,比如"123"會先被ToNumber轉(zhuǎn)換為123穷绵,然后再執(zhí)行ToInt32轿塔。
??????雖然嚴(yán)格說來并非強(qiáng)制類型轉(zhuǎn)換(因?yàn)榉祷刂殿愋筒]有發(fā)生變化),但字位運(yùn)算符(如|和~)和某些特殊數(shù)字一起使用時會產(chǎn)生類似強(qiáng)制類型轉(zhuǎn)換的效果,返回另外一個數(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俩由。
??????然后作者說到~的用法
??????很多語言中會有“哨位值”毒嫡,用來表示特殊的含義,比如JavaScript中的indexOf()用-1表示沒有搜索到指定子串幻梯。對于這樣的方法如果我們直接用>=0或者===-1這樣的判斷兜畸,就是把方法的實(shí)現(xiàn)細(xì)節(jié)暴露出來了,而用~計算indexOf()的結(jié)果碘梢,剛好~-1
為0咬摇,是假值,其它結(jié)果是真值煞躬。
??????作者認(rèn)為if (~a.indexOf(..)
這樣的判斷比>=0或者===-1更簡潔肛鹏。
??????然后作者開始討論~~
??????~~
中的第一個~
執(zhí)行ToInt32并反轉(zhuǎn)字位,然后第二個~
再進(jìn)行一次字位反轉(zhuǎn)恩沛,即將所有字位反轉(zhuǎn)回原值在扰,最后得到的仍然是ToInt32的結(jié)果(只適用于32位數(shù)字)。
??????~~x能將值截除為一個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ù)字字符呈宇,否則會失敗并返回NaN好爬。
??????parseInt(..)針對的是字符串值,非字符串參數(shù)會首先被強(qiáng)制類型轉(zhuǎn)換為字符串甥啄。依賴這樣的隱式強(qiáng)制類型轉(zhuǎn)換并非上策存炮,應(yīng)該避免向parseInt(..)傳遞非字符串參數(shù)。
??????parseInt(..)第二個參數(shù)可以指定轉(zhuǎn)換的基數(shù)蜈漓。如果沒有傳第二個參數(shù)穆桂,ES5前parseInt(..)會根據(jù)字符串的第一個字符來自行決定基數(shù),從ES5開始parseInt(..)則默認(rèn)轉(zhuǎn)換為十進(jìn)制數(shù)(除非0x開頭)融虽。
??????如果使用不當(dāng)享完,parseInt會出現(xiàn)一些難以理解的結(jié)果,但其實(shí)并沒毛病有额。
??????比如:parseInt(1/0, 19)
般又,實(shí)際上是parseInt("Infinity", 19)
彼绷。第一個字符是"I",以19為基數(shù)時值為18茴迁。所以最后的結(jié)果是18寄悯,而非Infinity或者報錯。
??????還有一些例子:
??????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í)行什么操作?
??????舉幾個例子:
??????'42' + '0' // 420
??????42 + 0 // 42
??????[1, 2] + [3, 4] // 1,23,4
??????ES5規(guī)定嘉竟,如果某個操作數(shù)是字符串的話邦危,+將進(jìn)行拼接操作,否則執(zhí)行數(shù)字加法舍扰。
如果操作數(shù)是對象倦蚪,它會先被調(diào)用valueOf,獲取值边苹,如果valueOf得不到基本類型值陵且,就會調(diào)用它的toString。
??????再說個神奇的例子:
??????[] + {} // [object Object]
??????{} + [] // 0
??????后面再分析
??????然后討論從字符串強(qiáng)制轉(zhuǎn)換為數(shù)字的情況
??????比如說用-運(yùn)算符:
??????[3] - [2] // 1
??????[3, 4] - [2] // NaN
??????除了-運(yùn)算符个束,*和/也會將操作數(shù)強(qiáng)制轉(zhuǎn)換為數(shù)字慕购。
??????我自己在瀏覽器控制臺試了下
??????{} - 1 // -1
??????[] - 1 // -1
??????唉,也不知道作何解釋茬底。
4.3 布爾值到數(shù)字的隱式強(qiáng)制類型轉(zhuǎn)換
??????可以對布爾值用數(shù)學(xué)運(yùn)算符沪悲,它會被隱式轉(zhuǎn)換成數(shù)字0或1。有的時候這樣是有用的阱表。
4.4 隱式強(qiáng)制類型轉(zhuǎn)換為布爾值
??????會發(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中||和&&返回的并不是布爾值,而是兩個操作數(shù)中的一個(且僅一個)最爬。
??????||和&&首先會對第一個操作數(shù)(a和c)執(zhí)行條件判斷涉馁,第一個操作數(shù)如果不是布爾值,會被強(qiáng)制類型轉(zhuǎn)換成布爾值爱致。
??????對于||來說烤送,如果條件判斷結(jié)果為true就返回第一個操作數(shù)的值,如果為false就返回第二個操作數(shù)的值糠悯。&&則相反帮坚。
??????a || b 大致相當(dāng)于 a ? a : b
??????a && b 大致相當(dāng)于 a ? b : a
??????之所以說大致相當(dāng)牢裳,是因?yàn)槿绻鸻是一個復(fù)雜一些的表達(dá)式,用?:的寫法它可能被執(zhí)行兩次叶沛,而在a || b和a && b中a只執(zhí)行一次。
??????||常用來設(shè)默認(rèn)值忘朝。
??????&&常被代碼壓縮工具用來壓縮代碼灰署,如if (a) { foo(); }會被壓縮成a && foo()。
4.6 符號的強(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 + '' // 會報錯 Uncaught TypeError: Cannot convert a Symbol value to a string
5.寬松相等==和嚴(yán)格相等===
??????==允許在相等比較中進(jìn)行強(qiáng)制類型轉(zhuǎn)換,而===不允許悦昵。
5.1 兩者的性能
??????如果比較的兩個值類型不同肴茄,==會先進(jìn)行強(qiáng)制類型轉(zhuǎn)換。如果兩個值類型相同但指,則==和===使用相同的算法寡痰。
??????性能上兩者幾乎沒有差別,使用時棋凳,只需要考慮有沒有強(qiáng)制類型轉(zhuǎn)換的必要拦坠,有就用==,沒有就用===剩岳,不用在乎性能贞滨。
5.2 抽象相等
??????es5規(guī)范規(guī)定了“抽象相等比較算法”定義了==運(yùn)算符的行為。
??????它規(guī)定:
??????(1)如果兩個值的類型相同拍棕,就僅比較它們是否相等晓铆。(注意特例:NaN不等于NaN;+0等于-0)
??????(2)兩個對象之間比較绰播,如果兩個對象指向同一個值時即視為相等骄噪。
??????(3)兩個值類型不相同,會發(fā)生隱式強(qiáng)制類型轉(zhuǎn)換幅垮,強(qiáng)制類型轉(zhuǎn)換可能會發(fā)生在其中一個值腰池,也可能兩個都被轉(zhuǎn)換,之后再被進(jìn)行比較忙芒。
??????下面進(jìn)行更具體的分情況討論:
??????①字符串和數(shù)字之間的相等比較
??????前面說過示弓,==的兩個比較值的類型不一致時,會先進(jìn)行強(qiáng)制類型轉(zhuǎn)換呵萨。那么轉(zhuǎn)換哪個呢奏属?
??????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這樣的比較值會被轉(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ā)中如果要判斷一個值是否為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]會先被調(diào)用toPrimitive涵亏,得到"42"宰睡,然后"42" == 42又被強(qiáng)制類型轉(zhuǎn)換了一次變成42 == 42,判斷結(jié)果為true气筋。
??????Number對象拆内、String對象(平時我們不怎么顯式地去用)存在“拆封”,這個拆封的過程也會調(diào)用對象的toPrimitive宠默。
??????舉例:"abc" === new String("abc") // false
??????"abc" == new String("abc") // true
5.3 奇葩情況集錦
??????①給對象定義了奇葩的valueOf方法麸恍,然后就能看到各種壯觀的奇葩事情。
??????②各種假值的相等比較
??????由于強(qiáng)制類型轉(zhuǎn)換的原因,所以假值之間比較可能會有一些看起來難以理解的結(jié)果抹沪,比如[] == ''為真刻肄、[] == false為真,這都是因?yàn)閺?qiáng)制類型轉(zhuǎn)換融欧,想想Number('')為0敏弃、[].toString()為''就知道了。
??????③[] == ![]為真
??????原因是這樣的噪馏,![]首先把[]轉(zhuǎn)成布爾類型值权她,得true,然后前面那個!把它轉(zhuǎn)成false逝薪,然后[] == false為真,因?yàn)樗鼈兌急晦D(zhuǎn)成數(shù)字了蝴罪。
??????對上述奇葩情況做總結(jié)董济,首先不要拿布爾值做==判斷,布爾值會被轉(zhuǎn)成數(shù)字的要门,然后就會看起來很靈異虏肾,咱犯不上那樣,布爾值就不要拿來==了欢搜。其次封豪,兩邊的值有[]、''或者0的炒瘟,盡量不要用==吹埠,這樣就能避開幾乎所有奇奇怪怪的強(qiáng)制類型轉(zhuǎn)換行為了。
??????作者小tips:對于typeof疮装,使用==是安全的缘琅,因?yàn)閠ypeof總是返回七個字符串之一,其中沒有空字符串廓推。所以在類型檢查過程中不會發(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)专缠,分為兩個部分:比較雙方都是字符串和其他情況雷酪。
??????①比較雙方首先調(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為相同的類型惭嚣,除此之外別無他法遵湖。