前言
這篇文章是我讀《你不知道的js(中)》總結(jié)出來(lái)的筆記哥蔚,如有疑惑或是問(wèn)題請(qǐng)?jiān)谠u(píng)論區(qū)指出~
點(diǎn)擊查看我的github原文優(yōu)化版
從js這門語(yǔ)言設(shè)計(jì)之初,他的特性————強(qiáng)制類型轉(zhuǎn)換写隶,就被眾多開(kāi)發(fā)者詬病。在很多書籍中都被稱為危險(xiǎn)的讲仰,是js設(shè)計(jì)的一大缺陷慕趴。但大多數(shù)人認(rèn)為這個(gè)特性在開(kāi)發(fā)中有利有弊,需要合理地使用它鄙陡。
自我檢測(cè)
[] == ![] // -> true
如果你的答案為false冕房,你需要仔細(xì)閱讀一下本文的D部分~~
顯式類型轉(zhuǎn)換和隱式類型轉(zhuǎn)換
強(qiáng)制轉(zhuǎn)換經(jīng)常發(fā)生在動(dòng)態(tài)類型語(yǔ)言運(yùn)行時(shí)。我們經(jīng)常會(huì)寫類型轉(zhuǎn)換柔吼,如:
var a=1
var b=a+'' // 隱式 '1'
var c=String(a) // 顯式 '1'
這里的隱式和顯式是相對(duì)于開(kāi)發(fā)者而言的毒费。可以從代碼中看出來(lái)類型轉(zhuǎn)換的是顯式愈魏,反則為隱式觅玻。
A.抽象值操作
1.ToString
非字符串->字符串。
基本類型
null -> 'null'
undefined -> 'undefined'
true -> 'true'
1 -> '1'
1 * 1 000 000 000 000 000 000 000 -> '1e+21'
復(fù)雜類型
當(dāng)對(duì)象有自己的toString()
方法培漏,字符串化時(shí)就會(huì)調(diào)用該方法溪厘,使用其返回值。
const obj={
a:'test',
toString(){
return 'yeah~~'
}
}
//沒(méi)有自定義的toString()方法應(yīng)該返回[object Object]111牌柄,
console.log(obj+'111') // yeah~~111
JSON字符串化
對(duì)于大多數(shù)簡(jiǎn)單值來(lái)說(shuō)畸悬,JSON.stringify()
和toString()
的效果基本相同,序列化的結(jié)果總是字符串珊佣。有一個(gè)比較特殊的情況:
JSON.stringify('hello') // ""hello"" 含有雙引號(hào)的字符串
對(duì)于undefined蹋宦、function、symbol來(lái)說(shuō)會(huì)返回undefined咒锻,在數(shù)組中返回null冷冗、在對(duì)象中自動(dòng)忽略。
JSON.stringify(undefined) // undefined
JSON.stringify(function(){}) // undefined
JSON.stringify([function(){},2]) // "[null,2]"
JSON.stringify({a:function(){},b:2}) // "{"b":2}"
const obj={
a:'test',
toJSON(){
return 'yeah~~'
}
}
console.log(JSON.stringify(obj))
答案:
"yeah~~"
2.ToNumber
基本類型
true -> 1
false -> 0
undefined -> NaN
null -> 0
處理字符串失敗時(shí)返回NaN惑艇。
復(fù)雜類型
對(duì)象(包括數(shù)組)蒿辙,先被轉(zhuǎn)換為相應(yīng)的基本類型值拇泛,如果返回的是非數(shù)字的基本類型值,則按照上面的規(guī)則強(qiáng)制轉(zhuǎn)換為數(shù)字思灌。
將值轉(zhuǎn)換為相應(yīng)的基本類型值俺叭,先檢查該值是否有valueOf()方法,有并且返回基本類型值泰偿,則使用該值進(jìn)行強(qiáng)制類型轉(zhuǎn)換熄守;沒(méi)有就使用toString()的返回值進(jìn)行強(qiáng)制轉(zhuǎn)換。如果以上都不返回基類型值甜奄,產(chǎn)生TypeError錯(cuò)誤柠横。
const obj={
toString(){
return '1'
}
}
console.log({}) // NaN
console.log(Number(obj)) // 1
注意使用Object.create(null)
創(chuàng)建的對(duì)象窃款,無(wú)法進(jìn)行強(qiáng)制轉(zhuǎn)換课兄!是因?yàn)槠?code>[[Prototype]]為空,沒(méi)有valueOf()
和toString()
方法晨继。
3.ToBoolean
假值(falsy value)
js中的值可被分為兩類:可被強(qiáng)制轉(zhuǎn)換為false的值烟阐,和其他(可以被強(qiáng)制轉(zhuǎn)換為true的值)。
以下這些為假值:
undefined
null
fasle
+0 -0 NaN
""
雖然沒(méi)有明確規(guī)定紊扬,我們可以默認(rèn)除了這些值以外的所有值為真值蜒茄。
B.顯式強(qiáng)制類型轉(zhuǎn)換
字符串和數(shù)字之間的顯式轉(zhuǎn)換
一般通過(guò)String()
和Number()
這兩個(gè)內(nèi)建函數(shù)實(shí)現(xiàn)的。如:
String(1) // "1"
Number('1.25') // 1.25
通過(guò)一元運(yùn)算符以及toString()
也被認(rèn)為是顯示強(qiáng)制類型轉(zhuǎn)換餐屎。
+'25' // 25
日期顯示轉(zhuǎn)換為數(shù)字
一元運(yùn)算符有一個(gè)常用的用途是檀葛,將Date對(duì)象強(qiáng)制轉(zhuǎn)換為Unix時(shí)間戳,如:
+new Date() // 1516625381333
我們也可以使用更顯式的方法:
new Date().getTime() // 1516625518125
最好還是使用Date.now()
來(lái)獲得當(dāng)前的時(shí)間戳腹缩。
位操作符~
~運(yùn)算符屿聋,按位非,反轉(zhuǎn)操作符的比特位藏鹊。位操作符會(huì)強(qiáng)制操作數(shù)使用32位格式润讥,通過(guò)ToInt32實(shí)現(xiàn)(ToInt32先執(zhí)行ToNUmber強(qiáng)制轉(zhuǎn)換,之后再執(zhí)行ToInt32)盘寡。如果你不太明白他的運(yùn)算機(jī)制楚殿,請(qǐng)記住一個(gè)公式:
~4 -> -5
~x => -(x+1)
~在日常開(kāi)發(fā)中很少會(huì)用到,但在我們處理indexOf()
時(shí)竿痰,可以將結(jié)果強(qiáng)制轉(zhuǎn)換為真/假值脆粥。
const str='hello'
str.indexOf('a') // -1
~str.indexOf('a') //0 -> 假值
~~x還可以用來(lái)截除小數(shù)部分,如:
~~-22.8 -> -22
顯式解析數(shù)字字符串
解析和轉(zhuǎn)換的區(qū)別
使用parseInt()
將字符串解析為數(shù)字影涉,它與Number
的作用并不一樣:
- parseInt只能解析字符串变隔,傳入其他類型參數(shù),如true常潮、function(){}等弟胀,返回NaN。
- parseInt可以解析含有非數(shù)字字符的字符串,如
parseInt('2px')
將會(huì)解析為2孵户,Number則會(huì)返回NaN萧朝。
對(duì)于parseInt有一個(gè)經(jīng)典的例子,
parseInt(1/0,19) -> 18
這是因?yàn)?/0為Infinity夏哭,先被轉(zhuǎn)化為字符串'Infinity'
检柬,第一個(gè)字符為i,在js中有效數(shù)字為09和0i竖配,所以之后的n不會(huì)被解析何址,只解析到i為止,i為第18位进胯,所以輸出為18.
顯式轉(zhuǎn)換為布爾值
和上面的Number(),String()一樣用爪,Boolean()為顯式的ToBoolean強(qiáng)制類型轉(zhuǎn)換。但這個(gè)在開(kāi)發(fā)中并不常用胁镐,通常使用!!
來(lái)進(jìn)行強(qiáng)制類型轉(zhuǎn)換偎血。
在if()...
上下文中,如沒(méi)有使用Boolean()
或!!
轉(zhuǎn)成布爾值盯漂,則會(huì)進(jìn)行隱式轉(zhuǎn)換颇玷。但還是建議使用顯式轉(zhuǎn)換,讓代碼可讀性更高就缆。
C.隱式強(qiáng)制類型轉(zhuǎn)換
1.字符串和數(shù)字之間的隱式轉(zhuǎn)換
+/-操作符
+如何判斷是進(jìn)行字符串拼接帖渠,還是數(shù)值加法呢?
+的其中一個(gè)操作符為字符串(或是通過(guò)ToPrimitive抽象操作后轉(zhuǎn)換為字符串的值)則進(jìn)行字符串拼接竭宰,否則執(zhí)行數(shù)字加法空郊。
所以,通常上我們將空字符串與數(shù)值進(jìn)行拼接羞延,將其轉(zhuǎn)換為字符串渣淳。
const a='2'
const b=a-0
b // -> 2
通過(guò)-
也可將a強(qiáng)制轉(zhuǎn)換為數(shù)字,或者使用a*1
或a/1
伴箩,因?yàn)檫@兩個(gè)運(yùn)算符只適用于數(shù)字入愧,所以比較少見(jiàn)。
const a=[1]
const b=[3]
a-b // -> -2
2.隱式類型轉(zhuǎn)換為布爾值
在以下情況中嗤谚,非布爾值會(huì)被隱式轉(zhuǎn)換為布爾值棺蛛。
- if()中的判斷表達(dá)式
- for(;;)中的條件判斷表達(dá)式
- while(...)和do..while(..)循環(huán)中的條件表達(dá)式
- ? : 中的條件判斷表達(dá)式
- 邏輯運(yùn)算符 || 和 && 左邊的操作數(shù)。
但&&和||返回的值并不一定是布爾值巩步,而是兩個(gè)操作書中其中的一個(gè)旁赊。如:
123||'hello' // 123
42&&'abc' // 'abc'
null || 'hello' // ->'hello'
null && 'hello' // ->null
3.Symbol的強(qiáng)制類型轉(zhuǎn)換
ES6允許從符號(hào)到字符串得顯示類型轉(zhuǎn)換,但使用隱式轉(zhuǎn)換會(huì)報(bào)錯(cuò)椅野。
const s1=Symbol('test')
String(s1) -> "Symbol(test)"
''+s1 -> Uncaught TypeError: Cannot convert a Symbol value to a string
同時(shí)终畅,Symbol類型也不能被轉(zhuǎn)換為數(shù)字(無(wú)論是顯式還是隱式)籍胯,但可以被轉(zhuǎn)換為布爾值。
D.寬松相等( == )和嚴(yán)格相等( === )
==
允許在相等比較中進(jìn)行強(qiáng)制類型轉(zhuǎn)換离福,但===
則不允許杖狼。
寬松相等的轉(zhuǎn)換規(guī)則(==)
- 對(duì)于基本類型:兩個(gè)值的類型相同,則比較是否相等妖爷。
除了NaN(NaN是js中唯一不等于自身的值)和+0/-0(+0 === -0)蝶涩。類型不同的兩個(gè)值參考第三條。 - 對(duì)于對(duì)象(包括函數(shù)和數(shù)組):他們指向同一引用時(shí)絮识,即視為相等绿聘,不發(fā)生強(qiáng)制轉(zhuǎn)換。
- 在比較兩個(gè)不同類型的值時(shí)次舌,會(huì)發(fā)生隱式類型轉(zhuǎn)換熄攘,將其轉(zhuǎn)為相同的類型后再比較。
字符串和數(shù)字之間的相等比較
const a='12'
const b=12
a==b //true
a===b //false
規(guī)則為:==
兩邊垃它,哪邊為數(shù)值類型鲜屏,則另一邊轉(zhuǎn)為數(shù)值類型烹看。
其它類型和布爾類型之間的相等比較
const a='12'
const b=true
a==b // false a為真值国拇,為什么返回false
因?yàn)樵?code>==兩邊,哪邊為布爾類型惯殊,哪邊轉(zhuǎn)為數(shù)值類型=戳摺!
同樣土思,a==false
也會(huì)返回false
务热,因?yàn)檫@里的布爾值會(huì)被強(qiáng)制轉(zhuǎn)換為數(shù)字0.
null和undefined之間的相等比較
只要記住:
null == undefined //true
null === undefined //false
對(duì)象和非對(duì)象之間的相等比較
對(duì)于布爾值和對(duì)象之間的比較己儒,先把布爾值轉(zhuǎn)換為數(shù)值類型崎岂。
數(shù)值或字符串與對(duì)象之間的比較,對(duì)象先會(huì)調(diào)用ToPromitive
抽象操作闪湾,之后再轉(zhuǎn)為數(shù)值進(jìn)行比較冲甘。
const a=12
const b=[12]
a==b //true
b->'12'->12
const c=Object(null)
c==null //fasle 這里c被轉(zhuǎn)換為空對(duì)象{}
const d=Object(undefined)
d==undefined // fasle 這里d被轉(zhuǎn)換為空對(duì)象{}
const e=Object(NaN)
e==NaN // fasle 這里e被轉(zhuǎn)換為Number(NaN) -> NaN 但NaN不等于自身,所以為false
幾個(gè)典型的坑
// 小坑
"0" == false // -> true 這里false先被轉(zhuǎn)為0途样,"0"也會(huì)轉(zhuǎn)為0江醇,所以為true
"0" == "" // -> false 兩個(gè)都是字符串類型,直接比較
0 == '' // -> true 空字符串直接轉(zhuǎn)為0
false == [] // -> true false先轉(zhuǎn)為0何暇;[]空數(shù)組轉(zhuǎn)為''陶夜,之后ToNumber操作轉(zhuǎn)為0
// 大坑
[] == ![] // -> true [] 這里![]先被強(qiáng)制轉(zhuǎn)換為false,變成[]與fasle的比較裆站,之后fasle->0条辟;[]->''->0黔夭,所以為true。
2=[2] // -> true [2]->'2'->2 所以為true
''==[null] // true [null]->''
0=='\n' // -> true '\n'->''->0
'true'==true // -> false true->0;'true'->NaN羽嫡,所以為false
如果你還是一頭霧水的話纠修,請(qǐng)仔細(xì)閱讀D部分這幾種相互比較的規(guī)則和C部分的隱式類型轉(zhuǎn)換。只要記住厂僧,遇到兩個(gè)不同類型的值扣草,轉(zhuǎn)換優(yōu)先順序?yàn)椴紶栔?gt;對(duì)象>字符串>數(shù)字;每一步的轉(zhuǎn)換到相同類型的值即停止轉(zhuǎn)換颜屠,進(jìn)行比較判斷辰妙。
E.抽象關(guān)系比較
出現(xiàn)非字符串就先轉(zhuǎn)為數(shù)字類型;如果兩者都為字符串甫窟,按照字母順序來(lái)比較密浑,如:
['22']<['023'] // -> false 這里并不轉(zhuǎn)為數(shù)字,0在字母順序上小于2粗井,所以為false
22<['023'] // -> true
對(duì)于對(duì)象來(lái)說(shuō)尔破,也同樣是轉(zhuǎn)換成字符串,再進(jìn)行比較浇衬,如:
const a={a:1}
const b={a:2}
a>b // -> false
a<b // -> false
a==b // -> false
a<=b // -> true
a>=b // -> true
這個(gè)例子比較奇怪懒构,雖然他們轉(zhuǎn)成字符串都為[Object Object]
,但兩個(gè)對(duì)象的比較并不是轉(zhuǎn)為字符串耘擂,而是看他們的引用是否指向同一值胆剧。這里<=
被處理為!>
,所以為true
醉冤; >=
同理秩霍。
總結(jié)
在處理強(qiáng)制類型轉(zhuǎn)換時(shí)要十分消息,尤其是隱式強(qiáng)制類型轉(zhuǎn)換蚁阳。寫代碼的時(shí)候铃绒,要知道什么時(shí)候要寫顯式類型轉(zhuǎn)換,什么時(shí)候用隱式螺捐,努力讓代碼更清晰可讀~