教育行業(yè)A股IPO第一股(股票代碼 003032)

全國咨詢/投訴熱線:400-618-4000

js中為什么你不敢用 “==”

更新時間:2018年08月06日15時42分 來源:傳智播客 瀏覽次數(shù):

  文章引用:http://0313.name/archives/480

  前言

  類型轉(zhuǎn)換在各個語言中都存在,而在 JavaScript 中由于缺乏對其的了解而不慎在使用中經(jīng)常造成bug被人詬病。為了避免某些場景下的意外,甚至推崇直接使用 Strict Equality( === )來代替 ==。這確實能避免很多bug,但更是一種對語言不理解的逃避(個人觀點)。

  引入

  先拋出在 You Don’t Know JavaScript (中) 看到的一個例子

  [] == [] // false

  [] == ![] // true

  {} == !{} // false

  {} == {} // false

  是不是很奇怪?本文將從書中看到的知識與規(guī)范相結(jié)合,來詳細說明一下JavaScript在類型轉(zhuǎn)換時候發(fā)生的故事。

  類型轉(zhuǎn)換

  很多人喜歡說顯示類型轉(zhuǎn)換與隱式類型轉(zhuǎn)換,但個人感覺只是說法上的不同,實質(zhì)都在發(fā)生了類型轉(zhuǎn)換而已,故不想去區(qū)分他們了(感覺一萬個人有一萬種說法)

  僅在6大基本類型 null undefined number boolean string object 作討論 symbol未考慮

  舉個栗子

  var a = String(1)

  var b = Number('1')

  var c = 1 + ''

  var d = +'1'

  a,b直接調(diào)用了原生函數(shù),發(fā)生了類型轉(zhuǎn)換。c,d使用了+運算符的一些規(guī)則,發(fā)生了類型轉(zhuǎn)換。這些是很簡單的也是我們常用的。

  其實真正起作用的,是語言內(nèi)部對規(guī)范中抽象操作的實現(xiàn),接下來我們所說的 ToString, ToNumber, ToBoolean等都是抽象操作,而不是JS里對應的內(nèi)置函數(shù)

  ToString – 規(guī)范9.8

  按照以下規(guī)則轉(zhuǎn)化被傳遞的參數(shù)

  Argument Type Result

  Undefined “undefined”

  Null “null”

  Boolean true -> “true”

  false – > “false”

  Number NaN -> “NaN”

  +0 -0 -> “0”

  -1 -> “-1”

  infinity -> “Infinity”

  較大的數(shù)科學計數(shù)法 (詳見規(guī)范9.8.1)

  String 不轉(zhuǎn)換 直接返回

  Object 1. 調(diào)用ToPrimitive抽象操作, hint 為 String 將返回值作為 value

  2. 返回ToString(value)

  String(undefined) // "undefined"

  String(null) // "null"

  String(true) // "true"

  ToPrimitive 抽象操作下面會提及

  ToNumber – 規(guī)范9.3

  按照以下規(guī)則轉(zhuǎn)換被傳遞參數(shù)

  Argument Type Result

  Undefined NaN

  Null +0

  Boolean true -> 1

  false -> +0

  Number 直接返回

  String 如果不是一個字符串型數(shù)字,則返回NaN(具體規(guī)則見規(guī)范9.3.1)

  Object 1. 調(diào)用ToPrimitive抽象操作, hint 為 Number 將返回值作為 value

  2. 返回ToNumber(value)

  ToBoolean – 規(guī)范9.2

  按照以下規(guī)則轉(zhuǎn)換被傳遞參數(shù)

  Argument Type Result

  Undefined false

  Null false

  Boolean 直接返回

  Number +0 -0 NaN -> false

  其他為true

  String 空字符串(length為0) -> false

  其他為true

  Object true

  ToPrimitive – 規(guī)范9.1

  顧名思義,該抽象操作定義了該如何將值轉(zhuǎn)為基礎類型(非對象),接受2個參數(shù),第一個必填的要轉(zhuǎn)換的值,第二個為可選的hint,暗示被轉(zhuǎn)換的類型。

  按照以下規(guī)則轉(zhuǎn)換被傳遞參數(shù)

  Argument Type Result

  Undefined 直接返回

  Null 直接返回

  Boolean 直接返回

  Number 直接返回

  String 直接返回

  Object 返回一個對象的默認值。一個對象的默認值是通過調(diào)用該對象的內(nèi)部方法[[DefaultValue]]來獲取的,同時傳遞可選參數(shù)hint。

  [[DefaultValue]] (hint) – 規(guī)范8.12.8

  當傳遞的hint為 String 時候,

  如果該對象的toString方法可用則調(diào)用toString

  如果toString返回了一個原始值(除了object的基礎類型)val,則返回val

  如果該對象的valueOf方法可用則調(diào)用valueOf方法

  如果valueOf返回了一個原始值(除了object的基礎類型)val,則返回val

  拋出TypeError的異常

  當傳遞的hint為 Number 時候,

  如果該對象的valueOf方法可用則調(diào)用valueOf方法

  如果valueOf返回了一個原始值(除了object的基礎類型)val,則返回val

  如果該對象的toString方法可用則調(diào)用toString

  如果toString返回了一個原始值(除了object的基礎類型)val,則返回val

  拋出TypeError的異常

  hint的默認值為Number,除了Date object

  舉個栗子

  var a = {}

  a.toString = function () {return 1}

  a.valueOf = function () {return 2}

  String(a) // "1"

  Number(a) // 2

  a + '' // "2" ???????

  +a // 2

  a.toString = null

  String(a) // "2"

  a.valueOf = null

  String(a) // Uncaught TypeError: balabala

  似乎我們發(fā)現(xiàn)了一個很不合規(guī)范的返回值,為什么 a + ''不應該返回”1″嗎

  問題的答案其實很簡單 + 操作符會對兩遍的值進行 toPrimitive 操作。由于沒有傳遞 hint 參數(shù),那么就會先調(diào)用a.valueOf 得到2后因為+右邊是字符串,所以再對2進行ToString抽象操作后與””的字符串拼接。

  不要畏懼使用 ==

  基礎概念已經(jīng)了解了,那么在 == 中到底發(fā)生了什么樣的類型轉(zhuǎn)換,而導致了經(jīng)常產(chǎn)生出乎意料的bug,導致了它臭名昭著。

  抽象相等 – 規(guī)范11.9.3

  x == y 判斷規(guī)則如下:

  如果xy類型相同 (與嚴格相等判斷一致,不贅述了,詳見規(guī)范)

  如果 x 為 null y 為 undefined, 返回true

  如果 x 為 undefined y 為 null, 返回true

  如果 x 類型為 Number, y 類型為 String, 返回 x == ToNumber(y)

  如果 x 類型為 String, y 類型為 Number, 返回ToNumber(x) == y

  如果 x 類型為 Boolean, 返回 ToNumber(x) == y

  如果 y 類型為 Boolean, 返回 x == ToNumber(y)

  如果 x 類型為 String 或 Number, y 類型為 Object, 返回 x == ToPrimitive(y)

  如果 x 類型為 Object, y 類型為 String 或 Number, 返回 ToPrimitive(x) == y

  return false

  再看引入

  [] == [] // false

  // 1. 兩遍類型都為 Object,比較引用地址,不同返回false 搞定

  [] == ![] // true

  // 1. ![]強制類型轉(zhuǎn)換 變?yōu)?[] == false

  // 2. 根據(jù)規(guī)范第7條,返回 [] == ToNumber(false), 即 [] == 0

  // 3. 根據(jù)規(guī)范第9條,返回ToPromitive([]) == 0,數(shù)組的valueOf為本身,不是原始值,則返回toString()即 "" == 0

  // 4. 根據(jù)規(guī)范第5條,返回ToNumber("") == 0, 即 0 == 0

  // 5. 根據(jù)規(guī)范第1條,返回 true

  // 下面的不贅述了,分析類似上面

  {} == !{} // false

  {} == {} // false

  我們不難看出以下幾點

  其實在x y類型相同的時候,== 與 === 沒有任何區(qū)別。

  除了undefined與null, 大多數(shù)值都會轉(zhuǎn)換為相同類型后進行對比,也就是說 === 是 == 某些情況下必經(jīng)的步驟

  引用 << 你不知道的JS(中) >> 中的2句話

  如果兩遍的值中有 true 或者 false , 千萬不要使用 == (會被轉(zhuǎn)為數(shù)字0,1來進行判斷,會出現(xiàn)一些意外的情況)

  如果兩遍的值中有[]、””或者0,盡量不要使用 ==

  抽象比較

  先來看看這個例子

  var a = { b: 42 }

  var b = { b: 43 }

  a < b // false

  a == b // false

  a > b // false

  a <= b // true

  a >= b // true

  是不是感覺到世界又崩塌了???

  讓我們來仔細分析一下

  var a = { b: 42 }

  var b = { b: 43 }

  a < b // false

  // 1. 兩遍調(diào)用ToPrimitive, 返回[object Object] 兩遍一致 返回 false

  a == b // false

  // 兩遍不同的引用,返回false

  a > b // false

  // 同 a < b

  a <= b // true

  // 按規(guī)范其實是處理成 !(a > b) 所以為true

  a >= b // true

  所以在不相等比較的時候,我們最后還是進行手動的類型轉(zhuǎn)換較為安全

  總結(jié)

  深入了解類型轉(zhuǎn)換的規(guī)則,我們就可以很容易取其精華去其糟粕,寫出更安全也更簡潔可讀的代碼。



  作者:傳智播客前端與移動開發(fā)培訓學院

  首發(fā):http://web.itcast.cn

0 分享到:
和我們在線交談!