摘要:看下面的代碼和會(huì)對(duì)操作數(shù)執(zhí)行條件判斷,如果操作數(shù)不是布爾值,會(huì)先執(zhí)行類(lèi)型轉(zhuǎn)換后再執(zhí)行條件判斷。大家記住這個(gè)規(guī)則布爾值如果與其他類(lèi)型進(jìn)行抽象比較,會(huì)先用將布爾值轉(zhuǎn)換為數(shù)字再比較。
在上一篇中我們聊過(guò)了 JS 類(lèi)型轉(zhuǎn)換的規(guī)則和我發(fā)現(xiàn)的一些常見(jiàn)書(shū)籍中關(guān)于類(lèi)型轉(zhuǎn)換的一些小錯(cuò)誤,當(dāng)碰到顯示類(lèi)型轉(zhuǎn)換的時(shí)候大家可以按照這些規(guī)則去拆解出答案。但 JS 中存在一些很隱晦的隱式類(lèi)型轉(zhuǎn)換,這一篇就來(lái)談下我對(duì)隱式類(lèi)型轉(zhuǎn)換的一些總結(jié)。
關(guān)于 JS 類(lèi)型轉(zhuǎn)換規(guī)則請(qǐng)看上一篇的內(nèi)容:掌握 JS 類(lèi)型轉(zhuǎn)換:從規(guī)則開(kāi)始
什么是隱式類(lèi)型轉(zhuǎn)換呢?顧名思義就是有時(shí)候你感覺(jué)不到這是類(lèi)型轉(zhuǎn)換但是實(shí)際上類(lèi)型轉(zhuǎn)換已經(jīng)發(fā)生了。所以這個(gè) "隱式" 取決于我們的理解和經(jīng)驗(yàn),如果你看不出來(lái)那就是隱式的。
下面按照我自己對(duì)于隱式轉(zhuǎn)換的分類(lèi)來(lái)逐個(gè)聊聊吧。
一元操作符 +、-var a = "123"; var b = +a; console.log(b); // 123
先來(lái)看看 + 或 - 在一個(gè)類(lèi)型值前面,這里會(huì)執(zhí)行 ToNumber 類(lèi)型轉(zhuǎn)換。如果是 - 在前面的話,還會(huì)將結(jié)果的符號(hào)取反,如:-"123" 的結(jié)果是 -123。并且如果原類(lèi)型是對(duì)象的話也是遵循 ToNumber 的轉(zhuǎn)換規(guī)則,大家可以自己試試,這里就不再舉多余的例子了。
二元操作符接下來(lái)我們來(lái)看一下二元操作符相關(guān)的隱式轉(zhuǎn)換,比如:+、-、&&、||、==等等這些。
相減 a - bvar a = "123"; var b = true; console.log(a - b); // 122
當(dāng)執(zhí)行減法操作時(shí),兩個(gè)值都會(huì)先執(zhí)行 ToNumber 轉(zhuǎn)換,所以這個(gè)是比較簡(jiǎn)單的,當(dāng)類(lèi)型是對(duì)象時(shí)也是遵循同樣的規(guī)則。
相加 a + bconsole.log("123" + 4); // "1234" console.log(123 + true); // 124
相加的情況有點(diǎn)復(fù)雜,但隱式轉(zhuǎn)換的規(guī)則大家可以按照我總結(jié)的來(lái)記:
如果 + 的操作數(shù)中有對(duì)象,則執(zhí)行 ToPrimitive 并且 hint 是 Number
如果 + 中有一個(gè)操作數(shù)是字符串(或通過(guò)第一步得到字符串),則執(zhí)行字符串拼接(另一個(gè)操作數(shù)執(zhí)行 ToString 轉(zhuǎn)換),否則執(zhí)行 ToNumber 轉(zhuǎn)換后相加
這個(gè)相加操作的隱式轉(zhuǎn)換規(guī)則看似有點(diǎn)麻煩,其實(shí)解析后還是很明確的。
第一步,先看操作數(shù)里面有沒(méi)有對(duì)象,如果有就是執(zhí)行 hint 是 Number 的 ToPrimitive 操作。大家可以回憶下上篇說(shuō)的 ToPrimitive 的內(nèi)容,這里要注意的是這里的 ToPrimitive 并沒(méi)有將操作數(shù)強(qiáng)制轉(zhuǎn)化為 Number 類(lèi)型。因?yàn)?hint 是 Number,所以先執(zhí)行 valueOf() ,如果返回了字符串那轉(zhuǎn)換結(jié)果就是字符串了;如果返回的不是基本類(lèi)型值才會(huì)執(zhí)行 toString(),如果都沒(méi)有返回基本類(lèi)型值就直接拋異常了。
第二步,如果有一個(gè)操作數(shù)是字符串,那么整個(gè)結(jié)果就是字符串拼接的,否則就是強(qiáng)轉(zhuǎn)數(shù)字加法;第二個(gè)操作數(shù)就會(huì)按這個(gè)規(guī)則進(jìn)行對(duì)應(yīng)的類(lèi)型轉(zhuǎn)換。
開(kāi)頭的代碼說(shuō)明了字符串加數(shù)字、數(shù)字加布爾值的結(jié)果按這個(gè)規(guī)則走的,下面我們來(lái)看看對(duì)象情況下的代碼:
var a = Object.create(null); a.valueOf = function() { return "123"; } a.toString = function() { return "234"; } console.log(a + 6); // "1236"
以上的執(zhí)行結(jié)果說(shuō)明了執(zhí)行 ToPrimitive 并且 hint 是 Number 結(jié)論是正確的,因?yàn)?"123" 是 valueOf 返回的。兩個(gè)操作數(shù)相加的其他情況大家也可以自己試試,記住我上面的總結(jié)就完了。
a && b、a || b在 JS 中我們都知道 && 和 || 是一種"短路”寫(xiě)法,一般我們會(huì)用在 if 或 while 等判斷語(yǔ)句中。這一節(jié)我們就來(lái)說(shuō)說(shuō) && 和 || 出現(xiàn)的隱式類(lèi)型轉(zhuǎn)換。
我們通常把 && 和 || 稱(chēng)為邏輯操作符,但我覺(jué)得 《你不知道的 Javascript(中卷)》中有個(gè)說(shuō)法很好:稱(chēng)它們?yōu)?選擇器運(yùn)算符"??聪旅娴拇a:
var a = 666; var b = "abc"; var c = null; console.log(a || b); // 666 console.log(a && b); // "abc" console.log(a || b && c); // 666
&& 和 || 會(huì)對(duì)操作數(shù)執(zhí)行條件判斷,如果操作數(shù)不是布爾值,會(huì)先執(zhí)行 ToBoolean 類(lèi)型轉(zhuǎn)換后再執(zhí)行條件判斷。最后 && 和 || 會(huì)返回一個(gè)操作數(shù)的值還不是返回布爾值,所以稱(chēng)之為"選擇器運(yùn)算符"很合理。
這里有個(gè)可能很多人都不知道的情況是:在判斷語(yǔ)句的執(zhí)行上下文中,&& 和 || 的返回值如果不是布爾值,那么還會(huì)執(zhí)行一次 ToBoolean 的隱式轉(zhuǎn)換:
var a = 666; var b = "abc"; var c = null; if (a && (b || c)) { console.log("yes"); }
如果要避免最后的隱式轉(zhuǎn)換,我們應(yīng)該這樣寫(xiě):
if (!!a && (!!b || !!c)) { console.log("yes"); }a == b 和 a === b
從這里開(kāi)始是 JS 中隱式轉(zhuǎn)換最容易中坑的地方
首先我們先明確一個(gè)規(guī)則:"== 允許在相等比較中進(jìn)行類(lèi)型轉(zhuǎn)換,而 === 不允許。"
所以如果兩個(gè)值的類(lèi)型不同,那么 === 的結(jié)果肯定就是 false 了,但這里要注意幾個(gè)特殊情況:
NaN !== NaN
+0 === -0
ES5 規(guī)范定義了 == 為"抽象相等比較",即是說(shuō)如果兩個(gè)值的類(lèi)型相同,就只比較值是否相等;如果類(lèi)型不同,就會(huì)執(zhí)行類(lèi)型轉(zhuǎn)換后再比較。下面我們就來(lái)看看各種情況下是如何轉(zhuǎn)換的。
null == undefined這個(gè)大家記住就完了,null == undefined // true。也就是說(shuō)在 == 中 null 與 undefined 是一回事。
所以我們判斷變量的值是 null 或者 undefined 就可以這樣寫(xiě)了:if (a == null) {...}。
數(shù)字和字符串的抽象相等比較一個(gè)操作數(shù)是字符串一個(gè)是數(shù)字,則字符串會(huì)被轉(zhuǎn)換為數(shù)字后再比較,即是:ToNumber(字符串) == 數(shù)字。
var a = 666; var b = "666"; console.log(a == b); // true布爾值與其他類(lèi)型的抽象相等比較
注意,這里比較容易犯錯(cuò)了:
var a = "66"; var b = true; console.log(a == b); // false
雖然 "66" 是一個(gè)真值,但是這里的比較結(jié)果卻不是 true,很容易掉坑里。大家記住這個(gè)規(guī)則:布爾值如果與其他類(lèi)型進(jìn)行抽象比較,會(huì)先用 ToNumber 將布爾值轉(zhuǎn)換為數(shù)字再比較。
顯然 "66" == 1 的結(jié)果當(dāng)然是 false 咯。
對(duì)象與非對(duì)象的抽象相等比較先說(shuō)下規(guī)則:如果對(duì)象與非對(duì)象比較,則先執(zhí)行 ToPrimitive(對(duì)象),并且 hint 參數(shù)為空;然后得到的結(jié)果再與非對(duì)象比較。
這里值得注意的是:在 ToPrimitive() 調(diào)用中如果 hint 參數(shù)為空,那么 [[DefaultValue]] 的調(diào)用行為跟 hint 是Number 時(shí)一樣——先調(diào)用 valueOf() 不滿足條件再調(diào)用 toString()。
注意這里有個(gè)例外情況:如果對(duì)象是 Date 類(lèi)型,則 [[DefaultValue]] 的調(diào)用行為跟 hint 是 String 時(shí)一樣。
我們來(lái)測(cè)試一下是不是這樣的:
var a = Object.create(null); a.valueOf = function() { console.log("a.valueOf is invoking."); return 666; }; a.toString = function() { console.log("a.toString is invoking."); return "666"; }; console.log(a == 666); // a.valueOf is invoking. // true console.log(a == "456"); // a.valueOf is invoking. // false a.valueOf = undefined; console.log(a == "666"); // a.toString is invoking. // true
根據(jù)輸出來(lái)看依據(jù)上面的規(guī)則來(lái)解釋是 OK 的。
有一個(gè)開(kāi)源項(xiàng)目有張圖表可以方便大家去記憶 == 與 ===,點(diǎn)擊 這里 查看。a > b、a < b
按慣例先總結(jié)規(guī)則,情況略微復(fù)雜:
第一步:如果操作數(shù)是對(duì)象則執(zhí)行 ToPrimitive(對(duì)象),并且 hint 參數(shù)為空。
第二步:
如果雙方出現(xiàn)非字符串,則對(duì)非字符串執(zhí)行 ToNumber,然后再比較
如果比較雙方都是字符串,則按字母順序進(jìn)行比較
我們還是用代碼來(lái)測(cè)試下:
var a = Object.create(null); a.valueOf = function() { console.log("a.valueOf is invoking."); return "666"; }; a.toString = function() { console.log("a.toString is invoking."); return true; }; console.log(a > "700"); // a.valueOf is invoking. // false a.valueOf = undefined; console.log(a < 2); // a.toString is invoking. // true
這里注意下當(dāng)測(cè)試 a < 2 時(shí),toString() 返回了 true,然后會(huì)執(zhí)行 ToNumber(true) 返回 1,最后 1 < 2 的結(jié)果就是 true。
a ≥ b,a ≤ b最后這里也是一個(gè)比較容易中坑的地方。
根據(jù)規(guī)范 a ≤ b 會(huì)被處理為 a > b,然后將結(jié)果反轉(zhuǎn),即處理為 !(a > b);a ≥ b 同理按照 !(a < b) 處理。
我們來(lái)看個(gè)例子:
var a = { x: 666 }; var b = { x: 666 }; console.log(a >= b); // true console.log(a <= b); // true
這里 a 和 b 都是字面量對(duì)象,valueOf() 的結(jié)果還是對(duì)象,所以轉(zhuǎn)為執(zhí)行 toString(),結(jié)果都是"[object Object]",當(dāng)然 a < b 和 a > b 的結(jié)果都是 false,然后取反結(jié)果就是 true 了?!?和 ≥ 的結(jié)果都是 true,是不是有點(diǎn)出乎意料呢
總結(jié)上一篇寫(xiě)了 JS 類(lèi)型轉(zhuǎn)換的規(guī)則,這一篇寫(xiě)了隱式轉(zhuǎn)換中我總結(jié)的經(jīng)驗(yàn)和判斷法則。感覺(jué)已經(jīng)差不多了,剩下的就是實(shí)踐中自己去理解了,后續(xù)可能還會(huì)找一些比較坑的類(lèi)型轉(zhuǎn)換示例代碼寫(xiě)一篇拆解分析。
感謝大家花時(shí)間聽(tīng)我比比,歡迎 star 和關(guān)注我的 JS 博客:小聲比比 Javascript
參考資料ES5 規(guī)范注釋
《你不知道的 Javascript(中卷)》
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/104662.html
摘要:下面先看看涉及到的幾個(gè)函數(shù)以及他們的轉(zhuǎn)換規(guī)則,這個(gè)是需要記憶的內(nèi)容類(lèi)型轉(zhuǎn)換需要使用到的函數(shù)對(duì)于布爾值用到的是對(duì)于數(shù)值,用到的是當(dāng)然還有但是對(duì)于隱式類(lèi)型轉(zhuǎn)換的時(shí)候,調(diào)用的是前者。 javaScript類(lèi)型轉(zhuǎn)換規(guī)則 javaScript的類(lèi)型轉(zhuǎn)換其實(shí)一直是很多前端開(kāi)發(fā)人員很迷的地方,一會(huì)兒這里要轉(zhuǎn)換,一會(huì)兒那里又要轉(zhuǎn)換,總之就是一個(gè)大寫(xiě)的迷,因?yàn)樗[式類(lèi)型轉(zhuǎn)換的地方實(shí)在是太多了。 但其實(shí)...
摘要:結(jié)合實(shí)際中的情況來(lái)看,有意或無(wú)意中涉及到隱式類(lèi)型轉(zhuǎn)換的情況還是很多的。此外當(dāng)進(jìn)行某些操作時(shí),變量可以進(jìn)行類(lèi)型轉(zhuǎn)換,我們主動(dòng)進(jìn)行的就是顯式類(lèi)型轉(zhuǎn)換,另一種就是隱式類(lèi)型轉(zhuǎn)換了。 前言 相信剛開(kāi)始了解js的時(shí)候,都會(huì)遇到 2 ==2,但 1+2 == 1+2為false的情況。這時(shí)候應(yīng)該會(huì)是一臉懵逼的狀態(tài),不得不感慨js弱類(lèi)型的靈活讓人發(fā)指,隱式類(lèi)型轉(zhuǎn)換就是這么猝不及防。結(jié)合實(shí)際中的情況來(lái)看...
摘要:當(dāng)一個(gè)值為字符串,另一個(gè)值為非字符串,則后者轉(zhuǎn)為字符串。文章出自的個(gè)人博客 showImg(https://segmentfault.com/img/bVEWkS?w=3376&h=1312); JavaScript 是一門(mén)弱類(lèi)型語(yǔ)言,剛接觸的時(shí)候感覺(jué)方便快捷(不需要聲明變量類(lèi)型了耶?。?,接觸久了會(huì)發(fā)現(xiàn)它帶來(lái)的麻煩有的時(shí)候不在預(yù)期之內(nèi) 呵呵一笑,哪有這么夸張,可能有人看過(guò)這樣一段代碼 ...
摘要:首先,為了掌握好類(lèi)型轉(zhuǎn)換,我們要理解一個(gè)重要的抽象操作為什么說(shuō)這是個(gè)抽象操作呢因?yàn)檫@是內(nèi)部才會(huì)使用的操作,我們不會(huì)顯示調(diào)用到。基本規(guī)則中的類(lèi)型轉(zhuǎn)換總是返回基本類(lèi)型值,如字符串?dāng)?shù)字和布爾值,不會(huì)返回對(duì)象和函數(shù)。 Javascript 里的類(lèi)型轉(zhuǎn)換是一個(gè)你永遠(yuǎn)繞不開(kāi)的話題,不管你是在面試中還是工作寫(xiě)代碼,總會(huì)碰到這類(lèi)問(wèn)題和各種的坑,所以不學(xué)好這個(gè)那是不行滴。關(guān)于類(lèi)型轉(zhuǎn)換我也看過(guò)不少的書(shū)和各...
摘要:本文從底層原理到實(shí)際應(yīng)用詳細(xì)介紹了中的變量和類(lèi)型相關(guān)知識(shí)。內(nèi)存空間又被分為兩種,棧內(nèi)存與堆內(nèi)存。一個(gè)值能作為對(duì)象屬性的標(biāo)識(shí)符這是該數(shù)據(jù)類(lèi)型僅有的目的。 導(dǎo)讀 變量和類(lèi)型是學(xué)習(xí)JavaScript最先接觸到的東西,但是往往看起來(lái)最簡(jiǎn)單的東西往往還隱藏著很多你不了解、或者容易犯錯(cuò)的知識(shí),比如下面幾個(gè)問(wèn)題: JavaScript中的變量在內(nèi)存中的具體存儲(chǔ)形式是什么? 0.1+0.2為什...
閱讀 1819·2023-04-26 00:30
閱讀 3218·2021-11-25 09:43
閱讀 2955·2021-11-22 14:56
閱讀 3275·2021-11-04 16:15
閱讀 1227·2021-09-07 09:58
閱讀 2087·2019-08-29 13:14
閱讀 3180·2019-08-29 12:55
閱讀 1064·2019-08-29 10:57