JavaScript深刻之头疼的类型转换(下)

前言

举个例子:前端

console.log(1 + '1')

在 JavaScript 中,这是彻底能够运行的,不过你有没有好奇,为何 1 和 '1' 分属不一样的数据类型,为何就能够进行运算呢?git

这实际上是由于 JavaScript 自动的将数据类型进行了转换,咱们一般称为隐式类型转换。可是咱们都知道,+运算符既能够用于数字加法,也能用于字符串拼接,那在这个例子中,是将数字 1 转成字符串 '1',进行拼接运算?仍是将字符串 '1' 转成数字 1,进行加法运算呢?github

先卖个关子,虽然估计你也知道答案。今天,咱们就常见的隐式类型转化的场景进行介绍。面试

一元操做符 +

console.log(+'1');

当 + 运算符做为一元操做符的时候,查看 ES5规范1.4.6,会调用 ToNumber 处理该值,至关于 Number('1'),最终结果返回数字 1数组

那么下面的这些结果呢?微信

console.log(+[]);
console.log(+['1']);
console.log(+['1', '2', '3']);
console.log(+{});

既然是调用 ToNumber 方法,回想《JavaScript 深刻之头疼的类型转换(上)》中的内容,当输入的值是对象的时候,先调用 ToPrimitive(input, Number) 方法,执行的步骤是:闭包

  1. 若是 obj 为基本类型,直接返回
  2. 不然,调用 valueOf 方法,若是返回一个原始值,则 JavaScript 将其返回。
  3. 不然,调用 toString 方法,若是返回一个原始值,则JavaScript 将其返回。
  4. 不然,JavaScript 抛出一个类型错误异常。

+[] 为例,[] 调用 valueOf 方法,返回一个空数组,由于不是原始值,调用 toString 方法,返回 ""app

获得返回值后,而后再调用 ToNumber 方法,"" 对应的返回值是 0,因此最终返回 0函数

剩下的例子以此类推。结果是:工具

console.log(+['1']); // 1
console.log(+['1', '2', '3']); // NaN
console.log(+{}); // NaN

二元操做符 +

规范

如今 + 运算符又变成了二元操做符,毕竟它也是加减乘除中的加号

1 + '1' 咱们知道答案是 '11',那 null + 1[] + [][] + {}{} + {} 呢?

若是要了解这些运算的结果,不可避免的咱们要从规范下手。

规范地址:http://es5.github.io/#x11.6.1

不过此次就不直接大段大段的引用规范了,直接给你们讲简化后的内容。

到底当执行 + 运算的时候,会执行怎样的步骤呢?让咱们根据规范11.6.1 来捋一捋:

当计算 value1 + value2时:

  1. lprim = ToPrimitive(value1)
  2. rprim = ToPrimitive(value2)
  3. 若是 lprim 是字符串或者 rprim 是字符串,那么返回 ToString(lprim) 和 ToString(rprim)的拼接结果
  4. 返回 ToNumber(lprim) 和 ToNumber(rprim)的运算结果

规范的内容就这样结束了。没有什么新的内容,ToStringToNumberToPrimitive都是在《JavaScript 深刻之头疼的类型转换(上)》中讲到过的内容,因此咱们直接进分析阶段:

让咱们来举几个例子:

1.Null 与数字

console.log(null + 1);

按照规范的步骤进行分析:

  1. lprim = ToPrimitive(null) 由于null是基本类型,直接返回,因此 lprim = null
  2. rprim = ToPrimitive(1) 由于 1 是基本类型,直接返回,因此 rprim = null
  3. lprim 和 rprim 都不是字符串
  4. 返回 ToNumber(null) 和 ToNumber(1) 的运算结果

接下来:

ToNumber(null) 的结果为0,(回想上篇 Number(null)),ToNumber(1) 的结果为 1

因此,null + 1 至关于 0 + 1,最终的结果为数字 1

这个还算简单,看些稍微复杂的:

2.数组与数组

console.log([] + []);

依然按照规范:

  1. lprim = ToPrimitive([]),[]是数组,至关于ToPrimitive([], Number),先调用valueOf方法,返回对象自己,由于不是原始值,调用toString方法,返回空字符串""
  2. rprim相似。
  3. lprim和rprim都是字符串,执行拼接操做

因此,[] + []至关于 "" + "",最终的结果是空字符串""

看个更复杂的:

3.数组与对象

// 二者结果一致
console.log([] + {});
console.log({} + []);

按照规范:

  1. lprim = ToPrimitive([]),lprim = ""
  2. rprim = ToPrimitive({}),至关于调用 ToPrimitive({}, Number),先调用 valueOf 方法,返回对象自己,由于不是原始值,调用 toString 方法,返回 "[object Object]"
  3. lprim 和 rprim 都是字符串,执行拼接操做

因此,[] + {} 至关于 "" + "[object Object]",最终的结果是 "[object Object]"。

下面的例子,能够按照示例类推出结果:

console.log(1 + true);
console.log({} + {});
console.log(new Date(2017, 04, 21) + 1) // 这个知道是数字仍是字符串类型就行

结果是:

console.log(1 + true); // 2
console.log({} + {}); // "[object Object][object Object]"
console.log(new Date(2017, 04, 21) + 1) // "Sun May 21 2017 00:00:00 GMT+0800 (CST)1"

注意

以上的运算都是在 console.log 中进行,若是你直接在 Chrome 或者 Firebug 开发工具中的命令行直接输入,你也许会惊讶的看到一些结果的不一样,好比:

type1

咱们刚才才说过 {} + [] 的结果是 "[object Object]" 呐,这怎么变成了 0 了?

不急,咱们尝试着加一个括号:

type2

结果又变成了正确的值,这是为何呢?

其实,在不加括号的时候,{} 被当成了一个独立的空代码块,因此 {} + [] 变成了 +[],结果就变成了 0

一样的问题还出如今 {} + {} 上,并且火狐和谷歌的结果还不同:

> {} + {}
// 火狐: NaN
// 谷歌: "[object Object][object Object]"

若是 {} 被当成一个独立的代码块,那么这句话至关于 +{},至关于 Number({}),结果天然是 NaN,但是 Chrome 却在这里返回了正确的值。

那为何这里就返回了正确的值呢?我也不知道,欢迎解答~

== 相等

规范

"==" 用于比较两个值是否相等,当要比较的两个值类型不同的时候,就会发生类型的转换。

关于使用"=="进行比较的时候,具体步骤能够查看规范11.9.5

当执行x == y 时:

  1. 若是x与y是同一类型:

    1. x是Undefined,返回true
    2. x是Null,返回true
    3. x是数字:

      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
    4. x是字符串,彻底相等返回true,不然返回false
    5. x是布尔值,x和y都是true或者false,返回true,不然返回false
    6. x和y指向同一个对象,返回true,不然返回false
  2. x是null而且y是undefined,返回true
  3. x是undefined而且y是null,返回true
  4. x是数字,y是字符串,判断x == ToNumber(y)
  5. x是字符串,y是数字,判断ToNumber(x) == y
  6. x是布尔值,判断ToNumber(x) == y
  7. y是布尔值,判断x ==ToNumber(y)
  8. x不是字符串或者数字,y是对象,判断x == ToPrimitive(y)
  9. x是对象,y不是字符串或者数字,判断ToPrimitive(x) == y
  10. 返回false

以为看规范判断太复杂?咱们来分几种状况来看:

1. null和undefined

console.log(null == undefined);

看规范第二、3步:

  1. x是null而且y是undefined,返回true
  2. x是undefined而且y是null,返回true

因此例子的结果天然为 true

这时候,咱们能够回想在《JavaScript专题之类型判断(上)》中见过的一段 demo,就是编写判断对象的类型 type 函数时,若是输入值是 undefined,就返回字符串 undefined,若是是 null,就返回字符串 null

若是是你,你会怎么写呢?

下面是 jQuery 的写法:

function type(obj) {
    if (obj == null) {
        return obj + '';
    }
    ...
}

2. 字符串与数字

console.log('1' == 1);

结果确定是true,问题在因而字符串转化成了数字和数字比较仍是数字转换成了字符串和字符串比较呢?

看规范第四、5步:

4.x是数字,y是字符串,判断x == ToNumber(y)

5.x是字符串,y是数字,判断ToNumber(x) == y

结果很明显,都是转换成数字后再进行比较

3. 布尔值和其余类型

console.log(true == '2')

当要判断的一方出现 false 的时候,每每最容易出错,好比上面这个例子,凭直觉应该是 true,毕竟 Boolean('2') 的结果但是true,但这道题的结果倒是false。

归根到底,仍是要看规范,规范第六、7步:

6.x是布尔值,判断ToNumber(x) == y

7.y是布尔值,判断x ==ToNumber(y)

当一方出现布尔值的时候,就会对这一方的值进行ToNumber处理,也就是说true会被转化成1,

true == '2' 就至关于 1 == '2' 就至关于 1 == 2,结果天然是 false

因此当一方是布尔值的时候,会对布尔值进行转换,由于这种特性,因此尽可能少使用 xx == truexx == false 的写法。

好比:

// 不建议
if (a == true) {}

// 建议
if (a) {}
// 更好
if (!!a) {}

4. 对象与非对象

console.log( 42 == ['42'])

看规范第八、9步:

  1. x不是字符串或者数字,y是对象,判断x == ToPrimitive(y)
  2. x是对象,y不是字符串或者数字,判断ToPrimitive(x) == y

以这个例子为例,会使用 ToPrimitive 处理 ['42'],调用valueOf,返回对象自己,再调用 toString,返回 '42',因此

42 == ['42'] 至关于 42 == '42' 至关于42 == 42,结果为 true

到此为止,咱们已经看完了第二、三、四、五、六、七、八、9步,其余的一律返回 false。

其余

再多举几个例子进行分析:

console.log(false == undefined)

false == undefined 至关于 0 == undefined 不符合上面的情形,执行最后一步 返回 false

console.log(false == [])

false == [] 至关于 0 == [] 至关于 0 == '' 至关于 0 == 0,结果返回 true

console.log([] == ![])

首先会执行 ![] 操做,转换成 false,至关于 [] == false 至关于 [] == 0 至关于 '' == 0 至关于 0 == 0,结果返回 true

最后再举一些会让人踩坑的例子:

console.log(false == "0")
console.log(false == 0)
console.log(false == "")

console.log("" == 0)
console.log("" == [])

console.log([] == 0)

console.log("" == [null])
console.log(0 == "\n")
console.log([] == 0)

以上均返回 true

其余

除了这两种情形以外,其实还有不少情形会发生隐式类型转换,好比if? :&&等状况,但相对来讲,比较简单,就再也不讲解。

深刻系列

JavaScript 深刻系列目录地址:https://github.com/mqyqingfen...

JavaScript 深刻系列预计写十五篇左右,旨在帮你们捋顺JavaScript底层知识,重点讲解如原型、做用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

若是有错误或者不严谨的地方,请务必给予指正,十分感谢。若是喜欢或者有所启发,欢迎 star,对做者也是一种鼓励。

再其余

如今是校招季,淘系前端正在帮助21届同窗们收割大厂offer,专门建立了交流群,能够在这里提问题,交流面试经验,这里也提供了简历辅导和答疑解惑等服务,加淘小招微信邀你入群,微信号 taoxiaozhao233

相关文章
相关标签/搜索