一篇文章讓你輕松記住js的隱式轉化
前言
之前寫過一篇文章[[js讓人詬病的這些feature]]中提出過一個疑問.
這個問題一開始我想簡單瞭.認為隻要記住一些特性就可以瞭.所以直接用窮舉法來進行規律的總結.
但是當遇到console.log(Number([]))
的結果是0, 而console.log(Number([1, 2]))
的結果是NaN.都什麼亂七八糟的,裡面必有蹊蹺.雖然能夠強背背下來, 但是作為一個有追求的程序員還是要弄明白它是怎麼一回事的.
console.log({} - {}) // NaN console.log([] - []) // 0 console.log([] - [1, 2]) // NaN console.log([] == ![]) // true console.log({} == {}) // false
要理解上面打印的結果,就是要理解Number([])
的值,Number([1, 2])
的值, Number({})
的值, 以及Boolean([])
返回的值. 下面慢慢說道.
一、包裝類
Boolean()
Boolean隻有兩種結果,true和false.
- Boolean結果為false的類型,我們通常稱他為 falsey, 中文叫虛值. 這些值在 [[[js讓人詬病的這些feature]] 有所提及,即,
0、null、undefined、false、''、NaN
有些文章把-0和+0算成兩個
上面這些都是原始值轉原始值.其他的都是true
如果是引用值轉原始值都為true. 還有下面這些一時間想不起來的引用類型.
Boolean(/d/) Boolean(new Error()) console.log(Boolean(Symbol()))
擴展
還有一種容易弄混的typeof 返回結果, node環境中:
console.log(typeof Date()) console.log(typeof new Date()) console.log(Date()) // Thu Jan 13 202l2 22:29:36 GMT+0800 (中國標準時間) console.log(new Date()) // 2022-01-13T14:29:36.660Z
Number()
引用類型轉換Number
易錯點出現在Number() 上面. 尤其是引用類型轉化為原始類型. 瞭解瞭這個,開頭的例子就能夠理解瞭.
我們隻有在知道瞭Number(引用類型)的規則才能夠判斷引用類型轉化為原始類型的結果什麼,不然是不可能判斷得出的餓,靠猜走不遠.
- 我們假設有如下這麼一個對象
const obj = { toString() { return 2 }, valueOf() { return 1 } }
- 我們Number()包裹它
console.log(Number(obj))
打印出的結果是1. 哦,有意思的來瞭,
consot obj = { toString() { return 2 }, valueOf() { return {} } } console.log(Number(obj)) // 2
讓我們讓valueOf 返回的值是對象的時候, 打印出 2 , 反之則直接打印出String原始值,再轉化為number類型.
一般來說valueof 就是代表值,沒有意義,也不用處理. 值是什麼就是什麼,比如說[1, 2, 3].valueOf() 直接打印就是[1, 2, 3].
我們也可以通過這個方法來解決[[讓 a == 1 && a == 2 && a == 3 成立]]的問題.
所以我們很容易得出這麼一個規律: 當valueOf 返回值是引用類型的時候, 就去拿toString 返回的值. 展開來說就是:
- 如果valueOf返回原始值,就Number包裝之後返回
- 如果valueOf返回的對象,就去toString()方法中找
- 如果toString() 返回原始值,就Number包裝之後返回
- 如果toString()返回的是對象,且是自己重寫的.那麼就直接報錯
- 如果不是充血的,那麼就調用Obejct.prototye.toString方法
這裡顯然還涉及到瞭[[原型鏈]]的問題,所以說其實隱式轉化的問題不是想象中的那麼簡單的.
而我們創建的對象基本沒講過會創建這兩個方法.所以它很顯然就是繼承至Object上面的方法. 也就是說,我們在研究這個問題的是,就是在研究Object.prototype.toString.call()
返回的值.
console.log(Object.prototype.tostring.call('123')); console.log(Object.prototype.toString.call(123)); console.log(object.prototype.tostring.call(true)); console.log(Object.prototype.tostring.call(undefined)); console.log(Object.prototype.tostring.call(null)); console.log(Object.prototype.tostring.call(function){})); console.log(Object.prototype.toString.call([1,2,3])); console.log(Object.prototype.tostring.call(ff));
打印的結果如下:
[object string] [object Number] [object Boolean] [object Undefined] [object Null] [object Functionl] [object Arrayl] [object objectl]
原始類型轉Number
console.log(Number(undefined)) // NaN console.log(Number (null)) // 0 console.log(Number(true)) // 1 console.log(Number (false)) // 0 console.log(Number(NaN)) // NaN console.1og(Number (Infinity))// Infinity console.log(Number('') // 0 console.log(Number(' ') // 0 console.log(Number('123')) // 123
上面沒啥好說的, 背下就行. 需要註意的是Number的值,除瞭我們平時使用的的數字意義之外,還有NaN、Infinity.
還有這些混淆點是需要註意的:
console.log((123).toString()) // 123 console.log(undefined.toString()) // 報錯 console.log(nul.toString()) // 報錯 復制代碼
undefined 和 null 沒有包裝類, 本身又是基礎類型 ,自然沒有其他亂碼七糟的方法. 所以報錯.
擴展
Argument和document
console.log(Object.toString.call(argument)) // [object Argument] console.log(Object.toString.call(document)) // [object HTMLDocument]
HTMLDocument是瀏覽器給我們提供的對象類型.
由此也可以得知Object.prototype.toString 方法的運用之廣, 識別類型之多, 比起typeof 簡陋的返回值強大得多. 當然每個都有每個使用的場景就是瞭.
手寫typeof
typeof是jscore自帶,而且也不是語法糖. 我一開始看到這個面試題的時候是懵逼的. 難道要手寫typeof的引擎代碼? 解釋一下從js第一個版本就存在的typeof null為object嗎?
但是還真有公司考這個, 有點睿智,大聰明. 所以我看瞭看網上別人對於這的解析… 就是利用Object.prototype.toString.call()
返回的結果,在進行字符串的切割,之後後面那個單詞返回出去.
就這? 脫褲子放屁,多此一舉.
Number轉化非二進制
Number(0xfff) // 4095 Number(070) // 56
Number可以直接識別不同位數轉化成十進制.
parseInt和Number關系
Number('123abc') // NaN Number('ad123') // NaN Number(' 123') // 123
而[[parseInt]]就很好的解決瞭這個問題,它可以說是對於Number()很好的一個補充
parseInt('123abc') // 123 parseInt('123asd1') // 123 parseInt('ad123') // NaN parseInt(' 123') // 123
String()
Object.prototype.toString
對於String()的使用依舊使用Number()使用的例子
const obj = { toString() { return 2 }, valueOf() { return 1 } }
當我觸發String(obj)的時候,就和Number()完全相反.
console.log(String(obj)) // 2
直接訪問的是toString()方法.
const obj = { toString() { return {} }, valueOf() { return 1 } } console.log(String(obj)) // 1
但是如果toString() 返回的是引用類型的話, 就往valueOf()方法上面找. 可以說和Number()的完全相反,但是也符合情理 .
通過重寫toString()和valueOf()的方法來瞭解內部的運行規則是一種很好的方式.
如果不重寫的話,Object.prototype.toString.call(對象), 返回值參看Number()部分的內容.
console.log(String({})) // [object Object]
Array.prototype.toString
這個記憶上沒啥好說的, 直接把外面的[]給拆瞭就行.
console.log(String([1])) // '1' console.log(String([1, 2])) // '1, 2' console.log(Array.prototype.toString.call([1])) // '1' console.log(Array.prototype.toString.call([1, 2])) // '1, 2'
二、隱式轉化觸發規則
前面說瞭顯示轉化的規律. 下面是能夠觸發隱式轉化的規則.
和運算符規則是和[[運算符的優先級]],在這裡不提,可自行查閱.
佈爾的隱式轉化
當出現判斷的時候,會出現隱式轉化.
if, switch, while, for(; ;), &&, ||, !, !!, ? : 三元
number的隱式轉化
隻要有小學的知識都知道運算符,它是用於數字之間的計算的.在JavaScript中也是基本是一樣的.
+ - * % == ~~ & | ~ ^ << <<<
等, 位運算符 、算術運算符
隱式轉化最難的情況
== !== >= <= < >
如下例子, 也是面試的高頻題目,背下來之餘,還是要知道得到這樣結果的過程.
console.log([] == ![]) // true
個人覺得隱式轉化最復雜的就是這個例子瞭.再復雜大不瞭加上優先級. 回到這個例子中, 看似比較的是兩個數組,或者說兩個特殊對象.其實不全是.來解析這個例子:
- 看到 等號 這個比較運算法就應該明白 等號 兩邊都要轉化成Number類型
- 從左到右的話,Number([]), [] 是引用類型,無法直接拿到原始值
- valueOf拿不到值,就走
Array.prototype.toString.call([])
.從上面可以知道, 它返回的是去掉[],即字符串''. - 此時左邊為Number(''). 所以左邊返回的自然是0.
此時這個題目為0 == ![]
. 接下來右邊的轉化,這就簡單瞭.
- 在Boolean()一節當中,就可以知道,除瞭falsey之外,其他都是ture.而此時在!的加持下,[]會進行Boolean()
- 此時右邊為true. !true就為false
- Number(false)的結果為0
由上而得出 0 == 0 的結果為true.運用上面的知識點可以很好的解析問題,下面的這個例子就更加簡單瞭.
console.log({} == {}) // false console.log({} != {})
如果按照一樣的分析方式來解釋的話:
- 兩邊都Number()包裹住.
- toString()之後都是[obejct Object]
Number('[obejct Object]')
為NaN- 所以最後轉化為
console.log(NaN == NaN)
的比較
NaN和任何一個值比較都不想等
題目不難,但是綜合的東西還是有一點的. 這兩題解決瞭,隱式轉化的問題也就到頭瞭
三、特殊情況
最容易記住的就是字符串運算符.
console.log(1 + '2' + '2') // '122' console.log(1 + + '2' + '2') // '32' console.log('A' - 'B' + '2') // 'NaN2' console.log('A' - 'B' + 2) // NaN
js 字符串和任何數據類型想加都轉化為字符串麼?可以這麼說,處理symbol類型直接報錯之外.
console.log(typeof (+ '2')) // number
還有下面undfined和null的特殊情況
console.log(undefined == null) // true console.log(NaN == NaN) // fasle
- NaN的語意是not a number,很明顯瞭,指的就不是一個數字
- NaN在typeof中是number類型,但是它和任何數都不想.
四、工作不要使用
2022年瞭,我們隻需要瞭解==的運行機制就夠瞭. 都這個年份瞭,不需要在重申工作中使用==還是===的問題瞭吧
總結
到此這篇關於js隱式轉化的文章就介紹到這瞭,更多相關js的隱式轉化內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 分享JavaScript 類型判斷的幾種方法
- JS疑惑的數據類型及類型判斷方法詳解
- JavaScript中檢測數據類型的四種方法
- 一篇文章弄懂js中的typeof用法
- JavaScript數據結構常見面試問題整理