天天看點

js 中 雙等号 和 三等号 的差別

######原文位址:http://coolcao.com/2016/08/06/js%E4%B8%AD-%E5%92%8C-%E7%9A%84%E5%8C%BA%E5%88%AB/

操作數1 == 操作數2
操作數1 === 操作數2
           

##一、結論

1

==

抽象相等,比較時,會先進行類型轉換,然後再比較值。

2

===

嚴格相等,會比較兩個值的類型和值。

3 在明确知道操作數的資料類型情況下,建議使用

===

;否則,使用

==

二、比較過程

這裡呢,我去網上找了很多資料,發現都不太全面,是以直接看看ECMA的規範标準是怎麼樣描述的。

####Abstract Equality Comparison

The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:

  1.If Type(x) is the same as Type(y), then

   a. Return the result of performing Strict Equality Comparison x === y.

  2.If x is null and y is undefined, return true.

  3.If x is undefined and y is null, return true.

  4.If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).

  5.If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.

  6.If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.

  7.If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).

  8.If Type(x) is either String, Number, or Symbol and Type(y) is Object, return the result of the comparison x ==    ToPrimitive(y).

  9.If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison

   ToPrimitive(x) == y.

  10.Return false.

####==

  • 如果Type(x)和Type(y)相同,傳回x===y的結果
  • 如果Type(x)和Type(y)不同
    • 如果x是null,y是undefined,傳回true
    • 如果x是undefined,y是null,傳回true
    • 如果Type(x)是Number,Type(y)是String,傳回 x==ToNumber(y) 的結果
    • 如果Type(x)是String,Type(y)是Number,傳回 ToNumber(x)==y 的結果
    • 如果Type(x)是Boolean,傳回 ToNumber(x)==y 的結果
    • 如果Type(y)是Boolean,傳回 x==ToNumber(y) 的結果
    • 如果Type(x)是String或Number或Symbol中的一種并且Type(y)是Object,傳回 x==ToPrimitive(y) 的結果
    • 如果Type(x)是Object并且Type(y)是String或Number或Symbol中的一種,傳回 ToPrimitive(x)==y 的結果
    • 其他傳回false

####Strict Equality Comparison

The comparison x === y, where x and y are values, produces true or false. Such a comparison is performed as follows:

  1.If Type(x) is different from Type(y), return false.

  2.If Type(x) is Number, then

   a. If x is NaN, return false.

   b. If y is NaN, return false.

   c. If x is the same Number value as y, return true.

   d. If x is

+0

and y is

‐0

, return true.

   e. If x is ‐0 and y is +0, return true.

   f. Return false.

  3.If Type(x) is Number, then

   NOTE This algorithm differs from the SameValue Algorithm in its treatment of signed zeroes and NaNs.

####===

  • 如果Type(x)和Type(y)不同,傳回false
  • 如果Type(x)和Type(y)相同
    • 如果Type(x)是Undefined,傳回true
    • 如果Type(x)是Null,傳回true
    • 如果Type(x)是String,當且僅當x,y字元序列完全相同(長度相同,每個位置上的字元也相同)時傳回true,否則傳回false
    • 如果Type(x)是Boolean,如果x,y都是true或x,y都是false傳回true,否則傳回false
    • 如果Type(x)是Symbol,如果x,y是相同的Symbol值,傳回true,否則傳回false
    • 如果Type(x)是Number類型
      • 如果x是NaN,傳回false
      • 如果y是NaN,傳回false
      • 如果x的數字值和y相等,傳回true
      • 如果x是

        +0

        ,y是

        -0

        ,傳回true
      • 如果x是

        -0

        ,y是

        +0

        ,傳回true
      • 其他傳回false

####上面兩個片段是ecma262規範中對

===

==

計算過程的定義,我摘錄過來并做了翻譯。

####可能一時半會有點不好了解,慢慢解釋。

其中涉及到幾個es定義的抽象操作:

  • Type(x) : 擷取x的類型
  • ToNumber(x) : 将x轉換為Number類型
  • ToBoolean(x) : 将x轉換為Boolean類型
  • ToString(x) : 将x轉換為String類型
  • SameValueNonNumber(x, y) : 計算非數字類型x, y是否相同
  • ToPrimitive(x) : 将x轉換為原始值

這裡的每個操作都有其嚴格并複雜的定義,可以直接查閱ECMA規範文檔對其的詳細說明。

附上線上文檔位址:ECMA262-7th

###這裡看下SameValueNonNumber()和ToPrimitive()兩個操作。

####SameValueNonNumber (x, y)

The internal comparison abstract operation SameValueNonNumber(x, y), where neither x nor y are Number values, produces true or false. Such a comparison is performed as follows:

  1. Assert: Type(x) is not Number.

  2. Assert: Type(x) is the same as Type(y).

  3. If Type(x) is Undefined, return true.

  4. If Type(x) is Null, return true.

  5. If Type(x) is String, then

    a. If x and y are exactly the same sequence of code units (same length and same code units at

    corresponding indices), return true; otherwise, return false.

  6. If Type(x) is Boolean, then

    a. If x and y are both true or both false, return true; otherwise, return false.

  7. If Type(x) is Symbol, then

    a. If x and y are both the same Symbol value, return true; otherwise, return false.

  8. Return true if x and y are the same Object value. Otherwise, return false.

  • 斷言:Type(x)不是Number類型
  • 斷言:Type(x)和Type(y)不同
  • 如果Type(x)是Undefined,傳回true
  • 如果Type(x)是Null,傳回true
  • 如果Type(x)是String,當且僅當x,y字元序列完全相同(長度相同,每個位置上的字元也相同)時傳回true,否則傳回false
  • 如果Type(x)是Boolean,如果x, y都是true或x, y都是false傳回true,否則傳回false
  • 如果Type(x)是Symbol,如果x, y是相同的Symbol值,傳回true,否則傳回false
  • 如果x和y是同一個對象值,傳回ture,否則傳回false

這個SameValueNonNumber操作說的就是,如果x,y兩個值類型相同,但又不同時是Number類型時的比較是否相等的操作。

####ToPrimitive ( input [ , PreferredType ] )

The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType. The abstract operation ToPrimitive converts its input argument to a non‐Object type. If an object is capable of converting to more than one primitive type, it may use the optional hint PreferredType to favour that type.

Conversion occurs according to Table 9:

Table 9: ToPrimitive Conversions

Input Type Result
Undefined Return input.
Null Return input.
Boolean Return input.
Number Return input.
String Return input.
Symbol Return input.
Object Perform the steps following this table.

When Type(input) is Object, the following steps are taken:

  1. If PreferredType was not passed, let hint be “default”.

  2. Else if PreferredType is hint String, let hint be “string”.

  3. Else PreferredType is hint Number, let hint be “number”.

  4. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).

  5. If exoticToPrim is not undefined, then

    a. Let result be ? Call(exoticToPrim, input, « hint »).

    b. If Type(result) is not Object, return result.

    c. Throw a TypeError exception.

  6. If hint is “default”, let hint be “number”.

  7. Return ? OrdinaryToPrimitive(input, hint).

    When the abstract operation OrdinaryToPrimitive is called with arguments O and hint, the following steps are

    taken:

  8. Assert: Type(O) is Object.

  9. Assert: Type(hint) is String and its value is either “string” or “number”.

  10. If hint is “string”, then

    a. Let methodNames be « “toString”, “valueOf” ».

  11. Else,

    a. Let methodNames be « “valueOf”, “toString” ».

  12. For each name in methodNames in List order, do

    a. Let method be ? Get(O, name).

    b. If IsCallable(method) is true, then

      i. Let result be ? Call(method, O).

      ii. If Type(result) is not Object, return result.

  13. Throw a TypeError exception.

NOTE When ToPrimitive is called with no hint, then it generally behaves as if the hint were Number. However, objects may over‐ride this behaviour by de ining a @@toPrimitive method. Of the objects de ined in this speci ication only Date objects (see 20.3.4.45) and Symbol objects (see 19.4.3.4) over‐ride the default ToPrimitive behaviour. Date objects treat no hint as if the hint were String.

####ToPrimitive() 方法

轉換成原始類型方法。

js中的原始類型:

  • Null: null值.
  • Undefined: undefined 值.
  • Number: 所有的數字類型,例如0,1,3.14等 以及NaN, 和 Infinity.
  • Boolean: 兩個值true和false.
  • String: 所有的字元串,例如’abc’和’’.

    其他的都是’非原始’的,像Array,Function,Object等。

注意:typeof null 得到的結果是object。這裡是由于js在最初的設計的問題。但其實null應該是屬于原始類型的.

上面規範對 ToPrimitive 操作的說明大緻可翻譯如下:

ToPrimitive ( input [ , PreferredType ] )方法傳遞兩個參數input和PreferredType,其中PreferredType是可選的。input輸入,PreferredType可選的期望類型。

ToPrimitive 運算符把其值參數轉換為非對象類型。如果對象有能力被轉換為不止一種原語類 型,可以使用可選的 期望類型 來暗示那個類型。根據下表完成轉換:

輸入類型 結果
Undefined 不轉換
Null 不轉換
Boolean 不轉換
Number 不轉換
String 不轉換
Symbol 不轉換
Object 傳回該對象的預設值。(調用該對象的内部方法[[DefaultValue]]一樣)

當input的類型是Object時,按照下面步驟進行操作:

  • 如果PreferredType沒有傳,令hint為”default”
  • 如果PreferredType參數是String類型,那麼令hint為”string”
  • 如果PreferredType參數是Number類型,那麼令hint為”number”
  • 令exoticToPrim為GetMethod(input, @@toPrimitive).
  • 如果exoticToPrim不是undefined,那麼
    • 令result為 Call(exoticToPrim, input, « hint »).
    • 如果result類型不是Object,直接傳回result
    • 扔出TypeError異常
  • 如果hint為”default”,令hint為”number”
  • 傳回OrdinaryToPrimitive(input, hint)的值

當抽象操作OrdinaryToPrimitive被執行,傳遞參數O和hint,按照下面步驟執行:

  • 斷言:O的類型是Object
  • 斷言:hint的類型是String,它的值隻能是’string’或’number’
  • 如果hint是’string’
    • 令methodNames是« “toString”, “valueOf” »
  • 否則
    • 令methodNames是« “valueOf”, “toString” ».
  • 在有順序的methodNames清單中,對于每個項目:
    • 令method為Get(O, name).
    • 如果IsCallable(method)傳回true,然後
      • 令result為Call(method, O).
      • 如果result的類型不是Object,傳回result
  • 傳回TypeError異常

注意:當調用ToPrimitive而沒有傳hint參數時,預設情況下hint将被指派Number。但是可以重寫對象的@@toPrimitive方法來覆寫這個行為。在本規範中,隻有Date和Symbol兩個對象重寫了預設的ToPrimitive操作。對于Date對象,如果沒有傳hint,hint将被預設指派為String

###看到這裡如果你還沒暈,說明你很厲害,那你應該接着往下看;反之,你更應該往下看了

對于一個Object,ToPrimitive()方法是如何轉換成原始類型的呢,最後轉換成的原始類型的值又是多少呢?

下面來看看js的源碼實作:

// ECMA-262, section 9.1, page 30. Use null/undefined for no hint,
// (1) for number hint, and (2) for string hint.
function ToPrimitive(x, hint) {
  // Fast case check.
  // 如果為字元串,則直接傳回
  if (IS_STRING(x)) return x;
  // Normal behavior.
  if (!IS_SPEC_OBJECT(x)) return x;
  if (IS_SYMBOL_WRAPPER(x)) throw MakeTypeError('symbol_to_primitive', []);
  if (hint == NO_HINT) hint = (IS_DATE(x)) ? STRING_HINT : NUMBER_HINT;
  return (hint == NUMBER_HINT) ? %DefaultNumber(x) : %DefaultString(x);
}
           

預設情況下,如果不傳hint參數,x如果是Date類型,則hint為String,否則hint為Number類型。

然後根據hint将處理結果傳回。

例如對于

[10]

數組這個例子,預設hint應該為Number類型。

處理函數為 DefaultNumber(x) 。

那麼,DefaultNumber()又是如何處理的呢?

####DefaultNumber()方法:

// ECMA-262, section 8.6.2.6, page 28.
function DefaultNumber(x) {
  if (!IS_SYMBOL_WRAPPER(x)) {
    var valueOf = x.valueOf;
    if (IS_SPEC_FUNCTION(valueOf)) {
      var v = %_CallFunction(x, valueOf);
      if (%IsPrimitive(v)) return v;
    }

    var toString = x.toString;
    if (IS_SPEC_FUNCTION(toString)) {
      var s = %_CallFunction(x, toString);
      if (%IsPrimitive(s)) return s;
    }
  }
  throw %MakeTypeError('cannot_convert_to_primitive', []);
}
           
  • 首先通過valueOf 轉換,即 obj.valueOf()方法的傳回值
  • 如果 obj.valueOf()方法的傳回值是原始類型,那麼直接傳回
  • 如果不是,再通過 obj.toString()方法轉換
  • 如果obj.toString()傳回的是原始類型,直接傳回該值
  • 如果還不是原始類型,抛出不能轉換異常。

    ####DefaultString()方法:

// ECMA-262, section 8.6.2.6, page 28.
function DefaultString(x) {
  if (!IS_SYMBOL_WRAPPER(x)) {
    // 轉換為字元串原始類型時首先通過toString
    var toString = x.toString;
    if (IS_SPEC_FUNCTION(toString)) {
      var s = %_CallFunction(x, toString);
      if (%IsPrimitive(s)) return s;
    }

    // 否則通過valueOf
    var valueOf = x.valueOf;
    if (IS_SPEC_FUNCTION(valueOf)) {
      var v = %_CallFunction(x, valueOf);
      if (%IsPrimitive(v)) return v;
    }
  }
  // 否則抛出異常
  throw %MakeTypeError('cannot_convert_to_primitive', []);
}
           
  • 首先使用toString()轉換
  • 如果obj.toString()傳回的是原始類型的值,直接傳回該值
  • 如果不是,再使用obj.valueOf()轉換
  • 如果obj.valueOf()傳回的是原始類型的值,傳回該值
  • 如果還不是,抛出不能轉換異常

    ####簡單總結下:

  • ToPrimitive(input,hint)轉換為原始類型的方法,根據hint目标類型進行轉換。
  • hint隻有兩個值:String和Number
  • 如果沒有傳hint,Date類型的input的hint預設為String,其他類型的input的hint預設為Number
  • Number 類型先判斷 valueOf()方法的傳回值,如果不是,再判斷 toString()方法的傳回值
  • String 類型先判斷 toString()方法的傳回值,如果不是,再判斷 valueOf()方法的傳回值

###來兩個簡單的例子看一下ToPrimitive()的結果可能會更有效。

  1. 求 ToPrimitive([10])

    對于數組,預設的hint應該為Number ,是以先判斷 valueOf()

> [10].valueOf()
[10]
           

數組的valueOf()方法傳回的是數組本身,不是一個原始類型,我們繼續判斷 toString()

> [10].toString();
'10'
           

toString()傳回的是一個字元串 ‘10’ 是一個原始類型,是以,

ToPrimitive([10]) = '10'

2. ToPrimitive(function sayHi(){})

var sayHi = function (name) {
    console.log('hi ' + name);
}
console.log(sayHi.valueOf());
console.log(typeof sayHi.valueOf());
console.log(sayHi.toString());
console.log(typeof sayHi.toString());
           

結果:

[Function]
function
function (name) {
    console.log('hi ' + name);
}
string
           

先判斷 valueOf() ,傳回的是一個function類型,隻能繼續判斷 toString()傳回的是string類型的,是原始類型,是以,

ToPrimitive(sayHi)是源碼字元串。

有了上面費了這麼大勁準備的基礎知識,我們再看看

==

就簡單多了,我們回過頭來看 == 的那幾個例子。

首先 [10] == 10

類型不同,Type([10])是Object,而Type(10)是Number,我們應該判斷

ToPrimitive([10])==10

的結果

由上面的分析,ToPrimitive([10])的結果為 字元串’10’,是以結果變為要判斷 ‘10’ == 10

Type(‘10’)為String,(10)為Number,是以結果變為 ToNumber(‘10’) == 10

ToNumber(‘10’)的值是 數字 10 ,是以,最後要判斷的是 10==10

很明顯,類型相同,值也相同,最終傳回的就是 true

再來看 [] == 0

同樣道地理,ToPrimitive([])==’’

而 ToNumber(‘’)==0

是以 []==0 的結果是 true

[]==false也很簡單了

false是Boolean類型的,我們要比較 []==ToNumber(false)即 []==0,就是上面這個啊,結果是 true

既然[] == false傳回的是true,那麼,![] == false應該傳回的false吧,我靠,怎麼還是true?

首先,左邊 ![] 進行取反,要轉換成Boolean類型,然後取反。Boolean([])的結果是true,取反是false

也就是,最終比較的是 false==false 結果當然是 true 了。

從上面我們可以看出,[]==![]是成立的,神奇吧,對,js就是這麼神奇。

null == false的結果是false,== 運算規則的最後一條,前面所有的都不滿足,最後傳回false,沒什麼好糾結的。

null == false的結果是false,但這并不代表 null == true 。因為 null == true 最後走的還是最後一條,傳回false。

#全文畢

歡迎關注微信公衆号:Javall咖啡屋

每天更新各種網際網路技術(前後端、資料庫、中間件、設計模式、資料結構、算法)學習心得體會

js 中 雙等号 和 三等号 的差別