JavaScript 中 == 和 === 的区别

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://linsh-tech.blog.csdn.net/article/details/89638652

1. 引言

这是在 JavaScript 中用来进行数值和对象对比时常用的操作符,从定义上来看:

  • == :抽象相等,比较时会先进性类型转换,然后再比较值

  • === :严格相等,会比较两个值的类型和值

测试例子:

console.log('10'==10);  // true
console.log('10'===10); // false

 

2. ECMA 规范

上面的例子只是从最直观的角度展示两个操作符的差别,想要从底层原理上来剖析两者的区别,还需要回归到 ECMA 的规范,这里以 ECMAScript 2016/ECMA-262 7th 文档(即 ES 6 版本)中的内容作为参考依据:

Type(x) 标识 x 的类型,Type(y) 标识 y 的类型

2.1 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.

第 1、2 点比较容易理解,直接字面翻译即可:

  1. 如果 Type(x) 和 Type(y) 不同,返回 false;

  2. 如果 Type(x) 是 Number :

    • 假如 x 是 NaN ,返回 false

    • 假如 y 是 NaN ,返回 false

    • 假如 x 的数值与 y 相等,返回 true

    • 假如 x 是 +0 ,y 是 -0 ,返回 true

    • 假如 x 是 -0 ,y 是 +0 ,返回 true

    • 其他情况,返回 false

  3. 返回 SameValueNonNumber(x,y) 的结果

 

SameValueNonNumber

这是计算非 Number 类型 x, y 是否相同的方法,详细定义如下:

The internal comparison abstract operation SameValueNonNumber(x, y), where neither x nor y are Number values, producestrue 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.

  1. 断言 :Type(x) 不是 Number

  2. 断言 :Type(x) 和 Type(y) 相同

  3. 假如 Type(x) 是 Undefined ,返回 true

  4. 假如 Type(x) 是 Null ,返回 true

  5. 假如 Type(x) 是 String ,则

    当且仅当 x, y 字符序列相同(长度相同且每个位置上的字符也相同),返回 true ,否则,返回 false

  6. 假如 Type(x) 是 Boolean ,则

    x, y 都为 true 或都为 false ,返回 true ,否则,返回 false

  7. 假如 Type(x) 是 Symbol ,则

    当 x, y 具有相同 Symbol 值,返回 true,否则,返回 false

  8. 假如 x 和 y 是同一个对象值,返回 true,否则,返回 false

 

2.2 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), thena. 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.

  1. 假如 Type(x) 和 Type(y) 相同,则

    返回严格对比 x===y 的结果

  2. 假如 x 是 Null 而 y 是 Undefined ,返回 true

  3. 假如 x 是 Undefined 而 y 是 Null ,返回 true

  4. 假如 Type(x) 是 Number 而 Type(y) 是 String ,返回 x==ToNumber(y) 的结果

  5. 假如 Type(x) 是 String 而 Type(y) 是 Number ,返回 ToNumber(x)==y 的结果

  6. 假如 Type(x) 是 Boolean ,返回 ToNumber(x)==y 的结果

  7. 假如 Type(y) 是 Boolean ,返回 x==ToNumber(y) 的结果

  1. 假如 Type(x) 是 String 、Number 或 Symbol 其中之一,而 Type(y) 是 Object,则返回 x == ToPrimitive(y) 的结果

  2. 假如 Type(x) 是 Object 而 Type(y) 是 String 、Number 或 Symbol 其中之一,则返回 ToPrimitive(x)==y 的结果

  3. 其他情况,返回 false

 

ToPrimitive

用于将复杂数据类型转化为简单数据类型,或者说转换为原始类型(Null, Undefined, Number, String, Boolean 等),详细定义如下:

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 :

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 接口的定义其实是 ToPrimitive(input[, PreferredType])

  • 第一个参数input 是必选参数,传入需要进行转换的数据

  • 第二个参数 PreferredType 是可选参数,传入期望的转换后的数据类型,后面称之为 hint

当 input 的类型为 Null, Undefined, Number, String, Boolean 和 Symbol 这些数据类型时,不需要进行转换,直接返回。

当 input 是 Object 类型时,则

  1. 假如没有传入 PreferredType ,令 hint 为 “default”

  2. 假如 PreferredType 是 String,令 hint 为 "string"

  3. 假如 PreferredType 是 Number,令 hint 为 "number"

  4. 令 exoticToPrim 为 GetMethod(input, @@toPrimitive) 的返回值,其中 @@toPrimitive 是一个用于将对象转换成原始值的方法。

  5. 假如 exoticToPrim 不是 undefined ,则:

    • 令 result 为 Call(exoticToPrim, input, « hint »)

    • 假如 result 类型不是 Object ,直接返回 result

    • 抛出 TypeError 异常

  6. 假如 hint 是 “default” ,令 hint 为 “number”

  7. 返回 OrdinaryToPrimitive(input, hint) 的值

抽象操作 OrdinaryToPrimitive(O, hint) 执行的顺序如下:

  • 断言:O 的类型是 Object

  • 断言:hint 的类型必须是 String ,且字符串内容只能是 “number” 和 "string"

  • 假如 hint = “string” ,则

    令 methodNames 为 « “toString”, “valueOf” »

  • 假如 hint = “number” ,则

    令 methodNames 为 « “valueOf”, “toString” »

  • 对于 methodNames 每一个 name 依次执行如下操作:

    • 令 method 为 Get(O, name)

    • 假如 IsCallable(method) 返回 true,则继续执行:

      • 令 result 为 Call(method, O)

      • 如果 result 的类型不是 Object ,则返回 result

  • 返回 TypeError 异常

 

看起来上面的过程很复杂,但实际上有用的关键信息如下:

  • 假如传入的 hint 是String ,先判断 toString 能否调用,再判断 toString() 的结果,是基本类型才返回,再判断 valueOf 能否调用,再判断 valueOf() 的结果,是基本类型才返回,否则报错。

  • 假如传入的 hint 是 Number(或者没有 hint ,默认是 Number ),先判断 valueOf ,再判断 toString

  • 对于普通 Object,默认用 hint 为 Number 的方式来转换,对于 Date 类型的 Object ,用 hint 为 String 的方式来转换

 

3. 其他

上面提到的几个核心的接口 ToNumberToStringToPrimitive ,也是 JavaScript 隐式装箱 (隐式类型转换)操作的核心接口。

在分析如下的题目中:

[]+[]、{}+{}、[]+{}、{}+[]

这其实是 JavaScript 中加法运算相关的逻辑 12.8 Additive Operators

AdditiveExpression : AdditiveExpression + MultiplicativeExpression

  1. 把AdditiveExpression的result赋值给lref

  2. 把GetValue(lref)的结果赋值给lval

  3. 把MultiplicativeExpression的result赋值给rref

  4. 把GetValue(rref)的结果赋值给rval

  5. 把ToPrimitive(lval)的结果赋值给lprim

  6. 把ToPrimitive(lval)的结果赋值给rprim

  7. 如果Type(lprim)和Type(rprim)中有一个是String,则a.把ToString(lprim)的结果赋给lstrb.把ToString(rprim)的结果赋给rstrc.返回lstr和rstr拼接的字符串

  8. 把ToNumber(lprim)的结果赋给lnum

  9. 把ToNumber(rprim)的结果赋给rnum

  10. 返回lnum和rnum相加的数值

 

4. 参考


微信公众号「何乐不为呢」,一个码农的技术笔记和唠叨。

展开阅读全文

JavaScript中:表达式和语句的区别

09-17

本文要讲的是JavaScript中非常重要的两个知识点:表达式(expressions)和语句(statements)之间的区别.rn rn1.语句和表达式rnJavaScript中的表达式和语句是有区别的.一个表达式会产生一个值,它可以放在任何需要一个值的地方,比如,作为一个函数调用的参数.下面的每行代码都是一个表达式:rnmyvarrn3 + xrnmyfunc("a", "b")rn语句可以理解成一个行为.循环语句和if语句就是典型的语句.一个程序是由一系列语句组成的.JavaScript中某些需要语句的地方,你可以使用一个表达式来代替.这样的语句称之为表达式语句.但反过来不可以:你不能在一个需要表达式的地方放一个语句.比如,一个if语句不能作为一个函数的参数.rn2.其他语法rn看看下面这两对类似的语法,搞懂这些后,能够帮助我们更好的理解语句和表达式之间的关系.rn2.1 If语句和条件运算符rn下面是一个if语句的例子:rnvar x;rnif (y >= 0) rn x = y;rn else rn x = -y;rnrn类似if语句功能的表达式叫做条件运算符.上面的语句等价于下面的.rnvar x = (y >= 0 ? y : -y);rn在等于号=和分号;之间的代码就是条件表达式.两边的小括号不是必需的,但我觉得小括号能让条件表达式更易读.rn2.2 分号和逗号运算符rn在JavaScript中,使用分号可以连接两个语句:rnfoo(); bar()rn要想连接两个表达式,使用的是不常见的逗号运算符:rnfoo(), bar()rn逗号运算符会计算前后两个表达式,然后返回右边表达式的计算结果.例如:rn> "a", "b"rn'b'rnrn> var x = ("a", "b");rn> xrn'b'rnrn> console.log(("a", "b"));rnbrn3.看似语句的表达式rn一些表达式看起来像是语句,这可能会带来一些麻烦.rn3.1 对象字面量和语句块rn下面是一个对象字面量,也就是一个可以生成一个对象值的表达式.rnrn foo: bar(3, 5)rnrn不过同时,它也是一个完全合法的语句,这个语句的组成部分有:rn一个代码块:一个由大括号包围的语句序列.rn一个标签:你可以在任何语句前面放置一个标签.这里的foo就是一个标签.rn一条语句:表达式语句bar(3, 5).rn你也许会感到震惊,那就是JavaScript居然可以有独立的代码块(常见的代码块是依托于循环或者if语句的).下面的代码演示了这种代码块的作用:你可以给它设置一个标签然后跳出这个代码块.rnfunction test(printTwo) rn printing: rn console.log("One");rn if (!printTwo) break printing;rn console.log("Two");rn rn console.log("Three");rnrnrn> test(false)rnOnernThreernrn> test(true)rnOnernTwornThreern3.2 函数表达式和函数声明rn下面的代码是一个函数表达式:rnfunction () rn你还可以给这个函数表达式起一个名字,将它转变为一个命名(非匿名)的函数表达式:rnfunction foo() rn这个函数的函数名(foo)只存在于函数内部,比如,可以用它来做递归运算:rn> var fac = function me(x) return x <= 1 ? 1 : x * me(x-1) rn> fac(10)rn3628800rn> console.log(me)rnReferenceError: me is not definedrn一个命名的函数表达式从表面上看起来,和一个函数声明并没有什么区别.但他们的效果是不同的:一个函数表达式产生一个值(一个函数).一个函数声明执行一个动作:将一个函数赋值给一个变量. 此外,只有函数表达式可以被立即调用,函数声明不可以.rn3.3 解决冲突rn从3.1和3.2可以看出,有些表达式和语句在表面上看不出有什么区别.也就意味着,相同的代码,出现在表达式上下文和出现在语句上下文会表现出不同的作用.通常情况下,这两种上下文是没有交集的.但是,如果是表达式语句的话,会有一个重叠:也就是说,会有一些表达式出现在语句上下文上.为了解决这种歧义,JavaScript语法禁止表达式语句以大括号或关键字"function"开头:rnExpressionStatement :rn [lookahead ∉ "", "function"] Expression ;rn那么,如果你想写一个以那些标志开头的表达式语句,该怎办呢? 你可以把它放在一个括号内部,这样并不会改变运行结果,只会确保该表达式被解析在表达式上下文中.让我们看两个例子.第一个例子:eval会按照语句上下文解析它的参数.如果你想让eval返回一个对象,你必须在对象字面量两边加上一个括号.rn> eval(" foo: 123 ")rn123rn> eval("( foo: 123 )")rn foo: 123 rn第二个例子:下面的例子是一个立即执行的函数表达式.rn> (function () return "abc" ())rn'abc'rn如果你省略了小括号,你会得到一个语法错误(函数声明不可以是匿名的):rn> function () return "abc" ()rnSyntaxError: function statement requires a namern如果你添加上函数名,还会得到一个语法错误(函数声明不能被理解执行):rn> function foo() return "abc" ()rnSyntaxError: syntax errorrn另外一个能让表达式在表达式上下文上被解析的办法是使用一元运算符,比如 + 或者 !.但是,和使用括号不同的是,这些操作符会改变表达式的运行结果.如果你不关心结果的话,完全可以使用:rn> +function () console.log("hello") ()rnhellornNaNrnNaN是+作用在函数执行后的返回值undefined上的结果.rn译者注:我觉的没翻译明白,所以用拙劣的水平画了张图.rn原文:http://www.software8.co/wzjs/Javascript/rn 论坛

没有更多推荐了,返回首页