美文网首页让前端飞Web前端之路
关于 == 操作符里的隐式转换2018-08-18

关于 == 操作符里的隐式转换2018-08-18

作者: littleyu | 来源:发表于2018-08-18 01:33 被阅读5次

前言

类型转换在各个语言中都存在,而在 JavaScript 中由于缺乏对其的了解而不慎在使用中经常造成bug被人诟病。为了避免某些场景下的意外,甚至推崇直接使用 Strict Equality( === )来代替 ==(最新的eslint规则默认就是使用===)。这确实能避免很多bug,但更是一种对语言不理解的逃避(个人观点)。

来看几道经典的题目:

  [] == [] // false
  [] == ![] // true
  {} == !{} // false
  {} == {} // false
是不是很奇怪?本文将从书中看到的知识与规范相结合,来详细说明一下JavaScript在类型转换时候发生的故事。

JS的数据类型

首先,回想一下JS的类型都有什么。
原始值(primitives): undefined, null, booleans, numbers,strings, symbol(es6)
对象值(objects): Object

ToPrimitive

在发生转换的时候,js其实都是会将操作对象转化为原始的对象,这也是最为诟病的地方,因为js很难直接抛出错误,她会用一套自己的方法去理解我们的错误,并做相应的调整,哪怕这些错误我们是无意识的。所以我们要知道她的转换方式,才能做到知己知彼,对代码的控制更为精准。

签名:ToPrimitive(input, PreferredType?) //PreferredType: Number 或者 String
流程如下:

input为原始值,直接返回;
不是原始值,调用该对象的valueOf()方法,如果结果是原始值,返回原始值;
调用valueOf()不是原始值,调用此对象的toString()方法,如果结果为原始值,返回原始值;
如果返回的不是原始值,抛出异常TypeError。
其中PreferredType控制线调取valueOf()还是toString()。

ps: Date类型按照String去调用。

总结:==操作符

比较运算 x==y,其中x和y是值,产生true或者false。这样的比较如下方式进行:

一、若Type(x)与Type(y)相同,则:
   a.若Type(x)为Undefined,返回true。
   b.若Type(x)为Null,返回true。
   c.若Type(x)为Number,则:
    1.若x为NaN,返回false。
    2.若y为NaN,返回false。
    3.若x与y为相等数值,返回true。
    4.若x为+0且y为-0,返回true。
    5.若x为-0且y为+0,返回true。
    6.返回false。
   d.若Type(x)为String,则当x和y完全相等的字符序列(长度相等且相同字符在相同位置)时返回true。否则,返回false。
   e.当Type(x)为Boolean,当x和y同为true或者同为false时返回true。否则,返回false。
   f.当x和y为引用同一对象时返回true。否则,返回false。
二、若x为null且y为undefined,返回true。
三、若x为undefined且y为null,返回true。
四、若Type(x)为number且Type(y)为String,返回comparison x==ToNumber(y)的结果。
五、若Type(x)为String且Type(y)为Number,返回comparison ToNumber(x) == y的结果。
六、若Type(x)为Boolean,返回比较ToNumber(x) == y的结果。
七、若Type(x)为Boolean,返回比较x == ToNumber(y)的结果。
八、若Type(x)为String或Number,且Type(y)为Object,返回比较x == ToPrimitive(y)的结果。
九、若Type(x)为Object且Type(y)为String或Number,返回比较ToPrimitive(x) == y的结果。
十、返回false。

上图中的 toPrimitive 就是对象转基本类型。
这里来解析一道题目 [] == ![] // -> true ,下面是这个表达式为何为 true 的步骤
// [] 转成 true,然后取反变成 false
[] == false
// 根据第 8 条得出
[] == ToNumber(false)
[] == 0
// 根据第 10 条得出
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根据第 6 条得出
0 == 0 // -> true

备注

ToString

按照以下规则转化被传递的参数

ArgumentType Result
Undefined “undefined”
Null “null”
Boolean true -> “true”false – > “false”
Number 1:’NaN -> “NaN”。 2:+0 -0 -> “0”。 3:-1 -> “-1。 4:”infinity -> “Infinity”
String 不转换 直接返回
Object 1:调用ToPrimitive抽象操作, hint 为 String 将返回值作为 value。2:返回ToString(value)。
String(undefined) // "undefined"
String(null) // "null"
String(true) // "true"
ToNumber

按照以下规则转换被传递参数

Argument Type Result
Undefined NaN
Null +0
Boolean 1:true -> 1。 2:false -> +0。
Number 直接返回
String 如果不是一个字符串型数字,则返回NaN(具体规则见规范9.3.1)
Object 1:调用ToPrimitive抽象操作, hint 为 Number 将返回值作为 value。 2:返回ToNumber(value)。
ToBoolean

按照以下规则转换被传递参数

Argument Type Result
Undefined false
Null false
Boolean 直接返回
Number 1:+0 -0 NaN -> false。 2:其他为true。
String 1:空字符串(length为0) -> false。 2:其他为true。
Object true
ToPrimitive

顾名思义,该抽象操作定义了该如何将值转为基础类型(非对象),接受2个参数,第一个必填的要转换的值,第二个为可选的hint,暗示被转换的类型。

按照以下规则转换被传递参数

Argument Type Result
Undefined 直接返回
Null 直接返回
Boolean 直接返回
Number 直接返回
String 直接返回
Object 返回一个对象的默认值。一个对象的默认值是通过调用该对象的内部方法[[DefaultValue]]来获取的,同时传递可选参数hint。
[[DefaultValue]] (hint)

当传递的hint为 String 时候,

  • 如果该对象的toString方法可用则调用toString
      如果toString返回了一个原始值(除了object的基础类型)val,则返回val
        如果该对象的valueOf方法可用则调用valueOf方法
      如果valueOf返回了一个原始值(除了object的基础类型)val,则返回val
        抛出TypeError的异常
      当传递的hint为 Number 时候,
  • 如果该对象的valueOf方法可用则调用valueOf方法
      如果valueOf返回了一个原始值(除了object的基础类型)val,则返回val
        如果该对象的toString方法可用则调用toString
      如果toString返回了一个原始值(除了object的基础类型)val,则返回val
        抛出TypeError的异常
  • hint的默认值为Number,除了Date object
    举个栗子
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

似乎我们发现了一个很不合规范的返回值,为什么 a + ''不应该返回”1″吗

问题的答案其实很简单 + 操作符会对两遍的值进行 toPrimitive 操作。由于没有传递 hint 参数,那么就会先调用a.valueOf 得到2后因为+右边是字符串,所以再对2进行ToString抽象操作后与””的字符串拼接。

相关文章

  • 关于 == 操作符里的隐式转换2018-08-18

    前言 类型转换在各个语言中都存在,而在 JavaScript 中由于缺乏对其的了解而不慎在使用中经常造成bug被人...

  • js 隐式类型转换

    关系操作符(<, >, <=, >=) 相等操作符(==) 测试用例 可以看出,与前文所述隐式类型转换相符 BUG...

  • More_Effective_C++_笔记_102

    操作符 条款05 对定制的"类型转换函数"保持警惕 具有隐式类型转换的函数: 单自变量 constructors ...

  • JavaScript 深入浅出

    1.操作符背后的隐式转换关系 "1.23"-1.23----为NaNvar num="23";--最简单的转换为n...

  • C++类型转换

    C++的类型转换分为隐式转换和显式转换 隐式转换举例: int i=4; double d=i;//隐式转换 显式...

  • scala-隐式机制及Akka

    隐式机制及Akka 隐式转换 隐式转换和隐式参数时Scala中两个非常强大的功能,利用隐式转换和隐式参数,可以提供...

  • Scala基础——隐式转换

    隐式转换 Scala的隐式转换,其实最核心的就是定义隐式转换函数,即implicitconversion func...

  • 22、a=a+b与a+=b有什么区别吗?

    a=a+b与a+=b有什么区别吗? += 操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转...

  • 【Scala】Scala 隐式转换 implicit

    本篇结构: 前言 隐式转换类型 隐式转换的规则 -- 如何寻找隐式转换方法 参考博文 一、Implicit 简介 ...

  • Scala 隐式转换

    一、隐式转换 隐式转换需要执行隐式函数,隐式函数是以 implicit 关键字声明的带有单个参数的函数。隐式函数会...

网友评论

  • wxy1107:==确实很坑,还是用===把,

本文标题:关于 == 操作符里的隐式转换2018-08-18

本文链接:https://www.haomeiwen.com/subject/ceutiftx.html