在上一篇中咱們聊過了 JS 類型轉換的規則和我發現的一些常見書籍中關於類型轉換的一些小錯誤,當碰到顯示類型轉換的時候你們能夠按照這些規則去拆解出答案。但 JS 中存在一些很隱晦的隱式類型轉換,這一篇就來談下我對隱式類型轉換的一些總結。javascript
關於 JS 類型轉換規則請看上一篇的內容: 掌握 JS 類型轉換:從規則開始
什麼是隱式類型轉換呢?顧名思義就是有時候你感受不到這是類型轉換可是實際上類型轉換已經發生了。因此這個 "隱式" 取決於咱們的理解和經驗,若是你看不出來那就是隱式的。java
下面按照我本身對於隱式轉換的分類來逐個聊聊吧。git
var a = '123'; var b = +a; console.log(b); // 123
先來看看 +
或 -
在一個類型值前面,這裏會執行 ToNumber
類型轉換。若是是 -
在前面的話,還會將結果的符號取反,如:-"123"
的結果是 -123
。而且若是原類型是對象的話也是遵循 ToNumber
的轉換規則,你們能夠本身試試,這裏就再也不舉多餘的例子了。github
接下來咱們來看一下二元操做符相關的隱式轉換,好比:+
、-
、&&
、||
、==
等等這些。segmentfault
var a = '123'; var b = true; console.log(a - b); // 122
當執行減法操做時,兩個值都會先執行 ToNumber
轉換,因此這個是比較簡單的,當類型是對象時也是遵循一樣的規則。測試
console.log('123' + 4); // "1234" console.log(123 + true); // 124
相加的狀況有點複雜,但隱式轉換的規則你們能夠按照我總結的來記:es5
ToPrimitive
而且 hint 是 NumberToString
轉換),不然執行 ToNumber
轉換後相加這個相加操做的隱式轉換規則看似有點麻煩,其實解析後仍是很明確的。spa
第一步,先看操做數裏面有沒有對象,若是有就是執行 hint 是 Number 的 ToPrimitive
操做。你們能夠回憶下上篇說的 ToPrimitive
的內容,這裏要注意的是這裏的 ToPrimitive
並無將操做數強制轉化爲 Number 類型。由於 hint 是 Number,因此先執行 valueOf()
,若是返回了字符串那轉換結果就是字符串了;若是返回的不是基本類型值纔會執行 toString()
,若是都沒有返回基本類型值就直接拋異常了。code
第二步,若是有一個操做數是字符串,那麼整個結果就是字符串拼接的,不然就是強轉數字加法;第二個操做數就會按這個規則進行對應的類型轉換。對象
開頭的代碼說明了字符串加數字、數字加布爾值的結果按這個規則走的,下面咱們來看看對象狀況下的代碼:
var a = Object.create(null); a.valueOf = function() { return '123'; } a.toString = function() { return '234'; } console.log(a + 6); // "1236"
以上的執行結果說明了執行 ToPrimitive
而且 hint 是 Number 結論是正確的,由於 "123"
是 valueOf
返回的。兩個操做數相加的其餘狀況你們也能夠本身試試,記住我上面的總結就完了。
在 JS 中咱們都知道 &&
和 ||
是一種"短路」寫法,通常咱們會用在 if
或 while
等判斷語句中。這一節咱們就來講說 &&
和 ||
出現的隱式類型轉換。
咱們一般把 &&
和 ||
稱爲邏輯操做符,但我以爲 《你不知道的 Javascript(中卷)》中有個說法很好:稱它們爲"選擇器運算符"。看下面的代碼:
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
&&
和 ||
會對操做數執行條件判斷,若是操做數不是布爾值,會先執行 ToBoolean
類型轉換後再執行條件判斷。最後 &&
和 ||
會返回一個操做數的值還不是返回布爾值,因此稱之爲"選擇器運算符"很合理。
這裏有個可能不少人都不知道的狀況是:在判斷語句的執行上下文中,&&
和 ||
的返回值若是不是布爾值,那麼還會執行一次 ToBoolean
的隱式轉換:
var a = 666; var b = 'abc'; var c = null; if (a && (b || c)) { console.log('yes'); }
若是要避免最後的隱式轉換,咱們應該這樣寫:
if (!!a && (!!b || !!c)) { console.log('yes'); }
從這裏開始是 JS 中隱式轉換最容易中坑的地方
首先咱們先明確一個規則:"==
容許在相等比較中進行類型轉換,而 ===
不容許。"
因此若是兩個值的類型不一樣,那麼 ===
的結果確定就是 false 了,但這裏要注意幾個特殊狀況:
NaN !== NaN
+0 === -0
ES5 規範定義了 ==
爲"抽象相等比較",便是說若是兩個值的類型相同,就只比較值是否相等;若是類型不一樣,就會執行類型轉換後再比較。下面咱們就來看看各類狀況下是如何轉換的。
這個你們記住就完了,null == undefined // true
。也就是說在 == 中 null 與 undefined 是一回事。
因此咱們判斷變量的值是 null 或者 undefined 就能夠這樣寫了:if (a == null) {...}
。
一個操做數是字符串一個是數字,則字符串會被轉換爲數字後再比較,便是:ToNumber(字符串) == 數字
。
var a = 666; var b = '666'; console.log(a == b); // true
注意,這裏比較容易犯錯了:
var a = '66'; var b = true; console.log(a == b); // false
雖然 '66'
是一個真值,可是這裏的比較結果卻不是 true,很容易掉坑裏。你們記住這個規則:布爾值若是與其餘類型進行抽象比較,會先用 ToNumber
將布爾值轉換爲數字再比較。
顯然 '66' == 1
的結果固然是 false 咯。
先說下規則:若是對象與非對象比較,則先執行 ToPrimitive(對象)
,而且 hint
參數爲空;而後獲得的結果再與非對象比較。
這裏值得注意的是:在 ToPrimitive()
調用中若是 hint 參數爲空,那麼 [[DefaultValue]] 的調用行爲跟 hint 是Number 時同樣——先調用 valueOf()
不知足條件再調用 toString()
。
注意這裏有個例外狀況:若是對象是 Date 類型,則 [[DefaultValue]] 的調用行爲跟 hint 是 String 時同樣。
咱們來測試一下是否是這樣的:
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
根據輸出來看依據上面的規則來解釋是 OK 的。
有一個開源項目有張圖表能夠方便你們去記憶==
與===
,點擊 這裏 查看。
按慣例先總結規則,狀況略微複雜:
第一步:若是操做數是對象則執行 ToPrimitive(對象)
,而且 hint
參數爲空。
第二步:
ToNumber
,而後再比較咱們仍是用代碼來測試下:
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
這裏注意下當測試 a < 2
時,toString() 返回了 true,而後會執行 ToNumber(true)
返回 1,最後 1 < 2 的結果就是 true。
最後這裏也是一個比較容易中坑的地方。
根據規範 a ≤ b 會被處理爲 a > b,而後將結果反轉,即處理爲 !(a > b)
;a ≥ b 同理按照 !(a < b)
處理。
咱們來看個例子:
var a = { x: 666 }; var b = { x: 666 }; console.log(a >= b); // true console.log(a <= b); // true
這裏 a 和 b 都是字面量對象,valueOf() 的結果仍是對象,因此轉爲執行 toString(),結果都是'[object Object]'
,固然 a < b
和 a > b
的結果都是 false,而後取反結果就是 true 了。≤ 和 ≥ 的結果都是 true,是否是有點出乎意料呢
上一篇寫了 JS 類型轉換的規則,這一篇寫了隱式轉換中我總結的經驗和判斷法則。感受已經差很少了,剩下的就是實踐中本身去理解了,後續可能還會找一些比較坑的類型轉換示例代碼寫一篇拆解分析。
感謝你們花時間聽我比比,歡迎 star 和關注個人 JS 博客:小聲比比 Javascript