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

全國(guó)咨詢/投訴熱線:400-618-4000

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

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

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

  前言

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

  引入

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

  [] == [] // false

  [] == ![] // true

  {} == !{} // false

  {} == {} // false

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

  類型轉(zhuǎn)換

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

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

  舉個(gè)栗子

  var a = String(1)

  var b = Number('1')

  var c = 1 + ''

  var d = +'1'

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

  其實(shí)真正起作用的,是語言內(nèi)部對(duì)規(guī)范中抽象操作的實(shí)現(xiàn),接下來我們所說的 ToString, ToNumber, ToBoolean等都是抽象操作,而不是JS里對(duì)應(yīng)的內(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ù)科學(xué)計(jì)數(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 抽象操作下面會(huì)提及

  ToNumber – 規(guī)范9.3

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

  Argument Type Result

  Undefined NaN

  Null +0

  Boolean true -> 1

  false -> +0

  Number 直接返回

  String 如果不是一個(gè)字符串型數(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)為基礎(chǔ)類型(非對(duì)象),接受2個(gè)參數(shù),第一個(gè)必填的要轉(zhuǎn)換的值,第二個(gè)為可選的hint,暗示被轉(zhuǎn)換的類型。

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

  Argument Type Result

  Undefined 直接返回

  Null 直接返回

  Boolean 直接返回

  Number 直接返回

  String 直接返回

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

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

  當(dāng)傳遞的hint為 String 時(shí)候,

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

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

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

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

  拋出TypeError的異常

  當(dāng)傳遞的hint為 Number 時(shí)候,

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

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

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

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

  拋出TypeError的異常

  hint的默認(rèn)值為Number,除了Date object

  舉個(gè)栗子

  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)了一個(gè)很不合規(guī)范的返回值,為什么 a + ''不應(yīng)該返回”1″嗎

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

  不要畏懼使用 ==

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

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

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

  如果xy類型相同 (與嚴(yán)格相等判斷一致,不贅述了,詳見規(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. ![]強(qiáng)制類型轉(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

  我們不難看出以下幾點(diǎn)

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

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

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

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

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

  抽象比較

  先來看看這個(gè)例子

  var a = { b: 42 }

  var b = { b: 43 }

  a < b // false

  a == b // false

  a > b // false

  a <= b // true

  a >= b // true

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

  讓我們來仔細(xì)分析一下

  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ī)范其實(shí)是處理成 !(a > b) 所以為true

  a >= b // true

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

  總結(jié)

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



  作者:傳智播客前端與移動(dòng)開發(fā)培訓(xùn)學(xué)院

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

0 分享到:
和我們?cè)诰€交談!