從一道面試題提及—js隱式轉換踩坑合集

前方提醒: 篇幅較長,點個贊或者收藏一下,能夠在下一次閱讀時方便查找javascript

提到js的隱式轉換,不少人第一反應都是:坑。java

的確,對於不熟悉的人來講,js隱式轉換存在着不少的讓人沒法預測的地方,相信不少人都深受其害,因此,你們在開發過程當中,可能會使用===來儘可能避免隱式轉換。可是,爲了更加深刻的理解javascript,本着對知識渴望的精神,咱們來經過大量的例子分析分析js隱式轉換,熟悉js隱式轉換的規則,讓其在你的眼裏變成「顯式」。面試

從一道面試題提及

先來看看一個經典的面試題數組

定義一個變量a,使得下面的表達式結果爲truemarkdown

a == 1 && a == 2 && a == 3
複製代碼

還有這種操做?先試試看吧,定義a = true?oop

var a = true
  a == 1 && a == 2 && a == 3 // false
複製代碼

可是並無達到預期,好像觸碰到知識盲區了。。。不要緊,先放下吧,來看看幾個更坑的this

[] == ![] // true

  [] == 0 // true
  
  [2] == 2 // true

  ['0'] == false // true

  '0' == false // true

  [] == false // true

  [null] == 0 // true

  null == 0 // false

  [null] == false // true

  null == false // false

  [undefined] == false // true

  undefined == false // false
複製代碼

一臉懵逼? 沒關係!接下來帶你完徹底全的認識javascript的隱式轉換spa

javascript隱式轉換規則

1. ToString,ToNumber,ToBoolean,ToPrimitive

咱們須要先了解一下js數據類型之間轉換的基本規則,好比數字、字符串、布爾型、數組、對象之間的相互轉換。prototype

1.1 ToString

這裏所說的ToString可不是對象的toString方法,而是指其餘類型的值轉換爲字符串類型的操做。code

這裏咱們討論nullundefined布爾型數字數組普通對象轉換爲字符串的規則。

  • null:轉爲"null"
  • undefined:轉爲"undefined"
  • 布爾類型:truefalse分別被轉爲"true""false"
  • 數字類型:轉爲數字的字符串形式,如10轉爲"10"1e21轉爲"1e+21"
  • 數組:轉爲字符串是將全部元素按照","鏈接起來,至關於調用數組的Array.prototype.join()方法,如[1, 2, 3]轉爲"1,2,3",空數組[]轉爲空字符串,數組中的nullundefined,會被當作空字符串處理
  • 普通對象:轉爲字符串至關於直接使用Object.prototype.toString(),返回"[object Object]"
String(null) // 'null'
  String(undefined) // 'undefined'
  String(true) // 'true'
  String(10) // '10'
  String(1e21) // '1e+21'
  String([1,2,3]) // '1,2,3'
  String([]) // ''
  String([null]) // ''
  String([1, undefined, 3]) // '1,,3'
  String({}) // '[object Objecr]'
複製代碼

對象的toString方法,知足ToString操做的規則。

注意:上面所說的規則是在默認的狀況下,若是修改默認的toString()方法,會致使不一樣的結果

1.2 ToNumber

ToNumber指其餘類型轉換爲數字類型的操做。

  • null: 轉爲0
  • undefined:轉爲NaN
  • 字符串:若是是純數字形式,則轉爲對應的數字,空字符轉爲0, 不然一概按轉換失敗處理,轉爲NaN
  • 布爾型:truefalse被轉爲10
  • 數組:數組首先會被轉爲原始類型,也就是ToPrimitive,而後在根據轉換後的原始類型按照上面的規則處理,關於ToPrimitive,會在下文中講到
  • 對象:同數組的處理
Number(null) // 0
  Number(undefined) // NaN
  Number('10') // 10
  Number('10a') // NaN
  Number('') // 0 
  Number(true) // 1
  Number(false) // 0
  Number([]) // 0
  Number(['1']) // 1
  Number({}) // NaN
複製代碼

1.3 ToBoolean

ToBoolean指其餘類型轉換爲布爾類型的操做。

js中的假值只有falsenullundefined空字符0NaN,其它值轉爲布爾型都爲true

Boolean(null) // false
  Boolean(undefined) // false
  Boolean('') // flase
  Boolean(NaN) // flase
  Boolean(0) // flase
  Boolean([]) // true
  Boolean({}) // true
  Boolean(Infinity) // true
複製代碼

1.4 ToPrimitive

ToPrimitive指對象類型類型(如:對象、數組)轉換爲原始類型的操做。

  • 當對象類型須要被轉爲原始類型時,它會先查找對象的valueOf方法,若是valueOf方法返回原始類型的值,則ToPrimitive的結果就是這個值
  • 若是valueOf不存在或者valueOf方法返回的不是原始類型的值,就會嘗試調用對象的toString方法,也就是會遵循對象的ToString規則,而後使用toString的返回值做爲ToPrimitive的結果。

注意:對於不一樣類型的對象來講,ToPrimitive的規則有所不一樣,好比Date對象會先調用toString,具體能夠參考ECMA標準

若是valueOftoString都沒有返回原始類型的值,則會拋出異常。

Number([]) // 0
  Number(['10']) //10

  const obj1 = {
    valueOf () {
      return 100
    },
    toString () {
      return 101
    }
  }
  Number(obj1) // 100

  const obj2 = {
    toString () {
      return 102
    }
  }
  Number(obj2) // 102

  const obj3 = {
    toString () {
      return {}
    }
  }
  Number(obj3) // TypeError
複製代碼

前面說過,對象類型在ToNumber時會先ToPrimitive,再根據轉換後的原始類型ToNumber

  • Number([]), 空數組會先調用valueOf,但返回的是數組自己,不是原始類型,因此會繼續調用toString,獲得空字符串,至關於Number(''),因此轉換後的結果爲"0"
  • 同理,Number(['10'])至關於Number('10'),獲得結果10
  • obj1valueOf方法返回原始類型100,因此ToPrimitive的結果爲100
  • obj2沒有valueOf,但存在toString,而且返回一個原始類型,因此Number(obj2)結果爲102
  • obj3toString方法返回的不是一個原始類型,沒法ToPrimitive,因此會拋出錯誤

看到這裏,覺得本身徹底掌握了?別忘了,那道面試題和那一堆讓人懵逼的判斷還沒解決呢,本着對知識渴望的精神,繼續往下看吧。

2. 寬鬆相等(==)比較時的隱式轉換規則

寬鬆相等(==)嚴格相等(===)的區別在於寬鬆相等會在比較中進行隱式轉換。如今咱們來看看不一樣狀況下的轉換規則。

2.1 布爾類型和其餘類型的相等比較

  • 只要布爾類型參與比較,該布爾類型的值首先會被轉換爲數字類型
  • 根據布爾類型ToNumber規則,true轉爲1false轉爲0
false == 0 // true
  true == 1 // true
  true == 2 // false
複製代碼

以前有的人可能以爲數字2是一個真值,因此true == 2應該爲真,如今明白了,布爾類型true參與相等比較會先轉爲數字1,至關於1 == 2,結果固然是false

咱們平時在使用if判斷時,通常都是這樣寫

const x = 10
  if (x) {
    console.log(x)
  }
複製代碼

這裏if(x)x會在這裏被轉換爲布爾類型,因此代碼能夠正常執行。可是若是寫成這樣:

const x = 10
  if (x == true) {
    console.log(x)
  }
複製代碼

代碼不會按照預期執行,由於x == true至關於10 == 1

2.2 數字類型和字符串類型的相等比較

  • 數字類型字符串類型作相等比較時,字符串類型會被轉換爲數字類型
  • 根據字符串的ToNumber規則,若是是純數字形式的字符串,則轉爲對應的數字,空字符轉爲0, 不然一概按轉換失敗處理,轉爲NaN
0 == '' // true
  1 == '1' // true
  1e21 == '1e21' // true
  Infinity == 'Infinity' // true
  true == '1' // true
  false == '0' // true
  false == '' // true
複製代碼

上面比較的結果和你預期的一致嗎? 根據規則,字符串轉爲數字,布爾型也轉爲數字,因此結果就顯而易見了。

這裏就不討論NaN了,由於NaN和任何值都不相等,包括它本身。

2.3 對象類型和原始類型的相等比較

  • 對象類型原始類型作相等比較時,對象類型會依照ToPrimitive規則轉換爲原始類型
'[object Object]' == {} // true
  '1,2,3' == [1, 2, 3] // true
複製代碼

看一下文章開始時給出的例子

[2] == 2 // true
複製代碼

數組[2]是對象類型,因此會進行ToPrimitive操做,也就是先調用valueOf再調用toString,根據數組ToString操做規則,會獲得結果"2", 而字符串"2"再和數字2比較時,會先轉爲數字類型,因此最後獲得的結果爲true

[null] == 0 // true
  [undefined] == 0 // true
  [] == 0 // true
複製代碼

根據上文中提到的數組ToString操做規則,數組元素爲nullundefined時,該元素被當作空字符串處理,而空數組[]也被轉爲空字符串,因此上述代碼至關於

'' == 0 // true
  '' == 0 // true
  '' == 0 // true
複製代碼

空字符串會轉換爲數字0,因此結果爲true

試試valueOf方法

const a = {
    valueOf () {
      return 10
    }
    toString () {
      return 20
    }
  }
  a == 10 // true
複製代碼

對象的ToPrimitive操做會先調用valueOf方法,而且avalueOf方法返回一個原始類型的值,因此ToPrimitive的操做結果就是valueOf方法的返回值10

講到這裏,你是否是想到了最開始的面試題? 對象每次和原始類型作==比較時,都會進行一次ToPrimitive操做,那咱們是否是能夠定義一個包含valueOf方法的對象,而後經過某個值的累加來實現?

試一試

const a = {
    // 定義一個屬性來作累加
    i: 1,
    valueOf () {
      return this.i++
    }
  }
  a == 1 && a == 2 && a == 3 // true
複製代碼

結果正如你所想的,是正確的。固然,當沒有定義valueOf方法時,用toString方法也是能夠的。

const a = {
    // 定義一個屬性來作累加
    i: 1,
    toString () {
      return this.i++
    }
  }
  a == 1 && a == 2 && a == 3 // true
複製代碼

2.4 null、undefined和其餘類型的比較

  • nullundefined寬鬆相等的結果爲true,這一點你們都知道

其次,nullundefined都是假值,那麼

null == false // false
  undefined == false // false
複製代碼

竟然跟我想的不同?爲何呢? 首先,false轉爲0,而後呢? 沒有而後了,ECMAScript規範中規定nullundefined之間互相寬鬆相等(==),而且也與其自身相等,但和其餘全部的值都不寬鬆相等(==)

最後

如今再看前面的這一段代碼就明瞭了許多

[] == ![] // true

  [] == 0 // true
  
  [2] == 2 // true

  ['0'] == false // true

  '0' == false // true

  [] == false // true

  [null] == 0 // true

  null == 0 // false

  [null] == false // true

  null == false // false

  [undefined] == false // true

  undefined == false // false
複製代碼

最後想告訴你們,不要一味的排斥javascript的隱式轉換,應該學會如何去利用它,你的代碼中可能存在着不少的隱式轉換,只是你忽略了它,要作到知其然,並知其因此然,這樣纔能有助於咱們深刻的理解javascript。

(看了這麼久了,辛苦了,不過我也寫了好久啊,點個贊再走吧)

相關文章
相關標籤/搜索