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'

  1. 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咖啡屋
每天更新各种互联网技术(前后端、数据库、中间件、设计模式、数据结构、算法)学习心得体会


推荐阅读更多精彩内容

  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 6,280评论 0 11
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 9,309评论 0 39
  • Javascript 中有6种基本类型(包括 symbol),以及对象类型,他们在不同的运算中会被系统转化为不同是...
    faremax阅读 287评论 0 3
  • JavaScript 中 == 和 === 的区别 和其他语言有些不同,在 JavaScript 中除了用==操作...
    融合xx阅读 1,772评论 0 2
  • 被噩梦惊醒的一瞬 世界是如此透彻 汽笛穿过五百道院墙 在你的耳边翻滚 安静的人费力调整着粗重的呼吸 路灯旁,被人忽...
    紫章阅读 60评论 0 0