令人抓狂的JS类型转化

578 阅读3分钟

偶然看到几道题,顿时心情就不好了

[] + {};
{} + [];
[1,2] + [2,3];
[] == 0;
1 + + "22" + 3;

是不是很绝望,叱咤前端几年,居然被这几道题目给制裁了。如果上述题目都会做了,那么,请忽略本文,我说的都是废话。 现在开始吧。

[]+{}

[] + {}; //[object Object]
{} + []; //0

纳尼,换个顺序结果还变了。 首先普及下+作为二元运算符的作用:

如果某个操作数是字符串或者能够转换为字符串的话,+将进行拼接操作,否则执行数字相加。如果一个操作数是对象(包括数组),那么首先会调用valueOf,再调用toString()。

对普通对象来说,除非自行定义,否则toString()返回内部属性[[Class]]的值,如“[object object]”。数组因为有自己的toString()方法,它会直接进行调用。看个简单例子:

let a = {
  toString(){
    return 1;
  }
}
a+1;  //2
1+{c:1}; //1[object Object]
[1,2]+[2,3];  //"1,22,3"

对象a因为有toString(),所以他转化为字符串的时候,会调用它并返回1,所以a+1实际生效的是1+1。

而{c:1}没有定义toString,所以1+{c:1}会转化为1+“[object Object]”。

数组的toString会用逗号将各元素拼接起来,[1,2]+[2,3]实际转化为"1,2"+“2,3”。 那么换个顺序为什么执行结果会变? 原因是{}出现在左侧,被当成了一个独立的代码块,实际生效的是+[],一元操作符+号是用来强制转化为数字,[]被转化为了0,所以{}+[]结果是0。

1+ +"22" + 3

细心的读者应该有发现,两个加号间有个空格。如果没有这个空格就会报错,它会被识别成自加符号。 但是现在留了空格,这个+号就是和"22"绑定在一起了,它的作用是强制转化为数字,所以实际生效的是 1+22+3 = 26。 一元操作符+号,可能会出现在这里

let timestamp = +new Date; // 1537257889834

它将日期对象强制类型转化为了数字,返回结果是unix时间戳。这样的代码似乎可以装一波逼。

[] == 0

==号的计算结果会根据两边的数据类型来进行处理。

1、数字x和字符串y

返回x == ToNumber(y)的值

1 == '12'  //false
1 == '1'  //true

‘12‘转化为数字为12,自然和1是不相等的

2、其他类型x和布尔类型y

返回x ==ToNumber(y)的值。

'12' == true

在没看这篇文章前,这题肯定有人会答true吧。true被转化为1,实际生效的是‘12’ == 1,所以为false。

if(a){}  //正确用法
if(a == true){}  //错误用法,原理如上,42为真值,但不会等于true

同理

0 == false //true
1 == true //true

3、null 和undefined

null == undefined 为true,且它俩与所有其他值比较的结果都是false。

4、对象x和非对象y

返回ToPrimitive(x) == y的值。

ToPrimitive(obj)等价于:先计算obj.valueOf(),如果结果为原始值,则返回此结果;否则,计算obj.toString(),如果结果是原始值,则返回此结果;否则,抛出异常。

其实上文的加号运算符已经有讲到。但是有一点要注意,你可以重写ToPrimitive。回到之前的例子

let a = {
  toString(){
    return 1;
  },
  [Symbol.toPrimitive]() {
    return 2;
  }
}
a + 2; //4
a == 2; //true

该方法在转化基本类型时优先级最高。 回到题目[] == 0,数组的valueOf是自身,然后再调用toString()得到'',所以实际生效的是''==0。''又转化为数字0,所以[] == 0。 最后出几道题目,看看大家有没有掌握。

null == 0;
[] == ![];
[12] == 12;