Do JS, also did not carefully study the specific difference between == and === two operators, but in a practical and realistic attitude, online today to check the difference between them two, the final conclusion is:

'===' is strictly equal, comparing the type of the two values with the abstract equality of the value '=='. When comparing, the type conversion is performed first, and the values are compared laterCopy the code

And then I’m even more confused, cast first, I shit, how to cast, left to right or right to left?

Let’s start with a few examples:

console.log([10] == 10); //true console.log('10' == 10); //true console.log([] == 0); //true console.log(true == 1); //true console.log([] == false); //true console.log(! [] == false); //true console.log('' == 0); //true console.log('' == false); //true console.log(null == false); //false console.log(! null == true); //true console.log(null == undefined); //trueCopy the code

After reading these examples, I am more confused, what, I agreed to cast first, how to cast? All values except null==false are true. But! Null = = true is true? Why is that?

No way, more and more confused, obediently go to the ECMA specification, see how the standard is stated.

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. Return SameValueNonNumber(x, y).

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

= = =

  • Return false if Type(x) and Type(y) are different
  • If Type(x) and Type(y) are identical
    • If Type(x) is Undefined, return true
    • If Type(x) is Null, return true
    • If Type(x) is String, return true if and only if the sequence of x and y characters is exactly the same (the length is the same, and the characters at each position are the same), and false otherwise
    • If Type(x) is Boolean, return true if x and y are both true or x and y are both false, otherwise return false
    • Return true if Type(x) is Symbol, and false if x and y are the same Symbol value
    • If Type(x) is of Type Number
      • If x is NaN, return false
      • If y is NaN, return false
      • Return true if the numeric value of x is equal to y
      • Return true if x is +0 and y is -0
      • Return true if x is -0 and y is +0
      • Others return false

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.

= =

  • If Type(x) and Type(y) are the same, return x===y
  • If Type(x) and Type(y) are different
    • If x is null and y is undefined, return true
    • Return true if x is undefined and y is null
    • If Type(x) is Number and Type(y) is String, return x==ToNumber(y)
    • If Type(x) is String and Type(y) is Number, return ToNumber(x)==y
    • If Type(x) is Boolean, return ToNumber(x)==y
    • If Type(y) is Boolean, return x==ToNumber(y)
    • If Type(x) is one of a String or Number or Symbol and Type(y) is Object, return x==ToPrimitive(y)
    • If Type(x) is Object and Type(y) is either String or Number or Symbol, return ToPrimitive(x)==y
    • Others return false

The above two fragments are the ecma262 specification definitions of the === and == calculation process, which I have excerpted and translated. It might be a little confusing for a while, but explain it slowly.

There are several es – defined abstract operations involved:

  • Type(x) : Gets the Type of x
  • ToNumber(x) : converts x ToNumber type
  • ToBoolean(x) : converts x ToBoolean type
  • ToString(x) : converts x ToString
  • SameValueNonNumber(x,y) : Calculates whether non-numeric types x and y are the same
  • ToPrimitive(x) : Converts x to the original value

Each of these operations has its own strict and complex definition, which can be described in detail in the ECMA specification documentation. Attach the online documentation address: ECMA262-7th

Here we look at the SameValueNonNumber() and ToPrimitive() operations.

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.

Translation:

  • Assertion: Type(x) is not of Type Number
  • Assertion: Type(x) is different from Type(y)
  • If Type(x) is Undefined, return true
  • If Type(x) is Null, return true
  • If Type(x) is String, return true if and only if the sequence of x and y characters is exactly the same (the length is the same, and the characters at each position are the same), and false otherwise
  • If Type(x) is Boolean, return true if x and y are both true or x and y are both false, otherwise return false
  • Return true if Type(x) is Symbol, and false if x and y are the same Symbol value
  • Return true if x and y are the same object value, false otherwise

The SameValueNonNumber operation compares x and y equally if they are of the same type but not of Number.

ToPrimitive ( input [ , PreferredType ] )

The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType. The abstract Operation ToPrimitive Converted 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 an @@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 () method

To a primitive type method. Primitive types in js:

  • Null: a Null value.
  • Undefined: Undefined values.
  • Number: all numeric types, such as 0,1,3.14, NaN, and Infinity.
  • Boolean: Two values true and false.
  • String: all strings, such as’ ABC ‘and’ ‘. Others are ‘non-primitive’, such as Array,Function,Object, etc.

Note: Typeof NULL returns object. This is due to js problems in the original design. But null should be of primitive type.

The description of ToPrimitive operations in the above specification can be roughly translated as follows:

The ToPrimitive (input [, PreferredType]) method takes two parameters input and PreferredType, where PreferredType is optional. Input Indicates the input,PreferredType Indicates the optional desired type. The ToPrimitive operator converts its value argument to a non-object type. If an object has the ability to be cast to more than one primitive class type, an optional expected type can be used to indicate that type. Complete the conversion according to the following table:

Input type The results of
Undefined Don’t convert
Null Don’t convert
Boolean Don’t convert
Number Don’t convert
String Don’t convert
Object Returns the default value for this object. (same as calling the object’s internal method [[DefaultValue]])

When the input type is Object, follow these steps:

  • If the PreferredType is not passed, hint is “default”
  • If the PreferredType argument is String, give hint “String”
  • If the PreferredType argument is of type Number, hint is “Number”
  • Make exoticToPrim GetMethod(input, @@toprimitive).
  • If exoticToPrim is not undefined, then
    • Make result Call(exoticToPrim, input, « hint »).
    • If the result type is not Object, result is returned
    • Throws a TypeError exception
  • If hint is “default”, set hint to “number”
  • Returns the value OrdinaryToPrimitive(input, hint)

When the abstract OrdinaryToPrimitive operation is executed, pass the arguments O and hint, do as follows:

  • Assertion: O is of type Object
  • Assertion: Hint is of type String and can only be ‘String’ or ‘number’
  • If hint is’ string ‘
    • Make methodNames « “toString”, “valueOf” »
  • Otherwise,
    • Make methodNames « “valueOf”, “toString” ».
  • In the sequential methodNames list, for each project:
    • Let method be Get(O, name).
    • If IsCallable(method) returns true, then
      • Make result Call(method, O).
      • If result is not of type Object, result is returned
  • Returns a TypeError exception

Note: When ToPrimitive is called without passing the Hint argument, the hint is assigned Number by default. But you can override this behavior by overriding the object’s @@Toprimitive method. In this specification, only Date and Symbol override the default ToPrimitive operation. For Date objects, if no hint is passed, hint is assigned to String by default

I mean, 10,000 fucking horses are running through my mind, and so am I.

For an Object, how does the ToPrimitive() method convert to the primitive type, and how many values are converted to the primitive type? We may not be able to tell much from the table above. Let’s take a look at the js source code implementation.

Use null/undefined for no hint, // (1) for number hint, And (2) for string hint. Function ToPrimitive(x, hint) {// Fast case check.  // 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); }Copy the code

By default, if no hint argument is passed, x is a String if it is Date, and Number otherwise. The result is then returned according to the HINT.

For the [10] array example, the default hint should be Number.

The handler is DefaultNumber(x). What about DefaultNumber()?

Keep looking for the code

DefaultNumber () method:

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', []); }Copy the code
  • It starts with a valueOf conversion, the return valueOf the obj.valueof () method
  • If the value returned by the obj.valueof () method is of the original type, it is returned directly
  • If not, it is converted using the obj.tostring () method
  • If obj.tostring () returns a primitive type, return that value directly
  • If it is not a primitive type, throw an uncast exception.

DefaultString () method:

Ecma-262, Section 8.6.2.6, Page 28. Function DefaultString(x) {if (! IS_SYMBOL_WRAPPER(x)) {// convert toString primitives via toString var toString = x.tostring; if (IS_SPEC_FUNCTION(toString)) { var s = %_CallFunction(x, toString); if (%IsPrimitive(s)) return s; } 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', []); }Copy the code
  • The toString() conversion is used first
  • If obj.tostring () returns a value of the original type, return that value directly
  • If not, use the obj.valueof () conversion again
  • If obj.valueof () returns a valueOf the original type, that value is returned
  • If not, throw an untransformable exception

There is a good mess, a simple summary:

  • ToPrimitive(input,hint) Method of converting to the original type, according to the hint target type.
  • Hint has only two values: String and Number
  • If no hint is sent, the default hint for Date input is String, and the default hint for other types of input is Number
  • The Number type evaluates the return valueOf the valueOf() method first, and if not, the return valueOf the toString() method
  • The String type evaluates the return valueOf the toString() method and, if not, the valueOf() method

It might be more useful to look at the results of ToPrimitive() with two simple examples.

ToPrimitive([10]); ToPrimitive([10]);

> [10].valueOf()
[10]
Copy the code

The valueOf() method of an array returns the array itself, not a primitive type. Let’s go ahead and check toString()

> [10].toString();
'10'Copy the code

ToString () returns a string. ’10’ is a primitive type, so 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());
Copy the code

Results:

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

ToPrimitive(sayHi) is the source string. ToString (sayHi) is the source string. ToString (sayHi) is the source string.

With the basics that went to all the trouble above, it’s much easier to look at ==. Ha ha. I’m going a little bit further, so let’s go back to the examples where == =. First of all [10]==10 this

ToPrimitive([10])==10; ToPrimitive([10])==10

ToPrimitive([10]) = ’10’; ToPrimitive([10]) = ’10’; type (‘ 10 ‘) = String; So the result becomes ToNumber(‘ 10 ‘)== 10The value of ToNumber(‘ 10 ‘) is the number 10, so the final judgment is 10==10

Obviously, the same type, the same value, will return true

Look at [] = = 0 this same truth, ToPrimitive ([]) = = ‘and’ ToNumber (‘ ‘) = = 0

So []==0 results in true

[]==false false is Boolean, we want to compare []==ToNumber(false), that is, []==0, the result is true

Since []==false returns true, then! []==false []==false First, the left! [] negate, convert to Boolean, and negate. Boolean([]) returns true or false

That is, you end up comparing false==false and of course it’s true.

From the above we can see that []==! [] is true, magical, right, JS is so magical.

If null==false, the result is false, and the last rule of the == operation does not satisfy all the preceding rules. The result of null==false is false, but this does not mean null==true. Because null==true is still the last line, return false.

Well, this should be almost written here, it is already 3 o ‘clock in the morning, my brain can not move, well, the train of thought is also quite disorderly, the article may not be so organized, if you find any mistakes, please advise me.

There are a lot of hidden details, such as how to convert js ToNumber(),ToBoolean(), etc. The ecMA-262 specification has detailed instructions for this. This is the most official explanation for this.

Also, == involves implicit casting, < (less than sign) also involves implicit casting, so I’ll rehash < and JS implicit casting for another day.

Main references: