[ ] == ![ ] 竟然输出true?带你深入理解JS中的类型转换机制

214 阅读7分钟

[]==![]竟然输出true?是一道常见的面试题,当你碰到的时候可能会蒙一个true,但是面试官就会接着问你为什么?这就需要深入理解JS中的类型转换机制了,本篇文章就让我们来看看JS中的类型转换是怎么转换的,看完相信你就可以清晰地回答这个问题了。

一. == VS ===

先来看下两个等号与三个等号的区别。

image.png

可以看出两个等号比的是数值,而三个等号比的是数值加类型,也就是说==会发生隐式转换,会隐式地将字符串'1'转换为数字1,然后数字1跟1比较,所以得出true,而===就不会发生隐式转换了,接下来就让我们来聊聊JS中类型转换机制。

二. 原始类型之间的转换

原始类型之间的转换一般只涉及到转布尔,转数字和转字符串。

2.1 转布尔

数字和字符串转布尔值,只要数字不为0,字符串不为空的话就是true,其它则是false。

console.log(Boolean(undefined));  //false
console.log(Boolean(null));   //false
console.log(Boolean(0));    //false
console.log(Boolean(NaN));    //false
console.log(Boolean(''));   //false

console.log(Boolean(-100));   //true
console.log(Boolean(50));    //true
console.log(Boolean('hello'));    //true

2.2 转数字

当字符串里面是个数字时,就能转成相应的数字,字符串为空时转成0,true转1,false转0,null转化0,undefined转成NaN,当然这些猜基本都能猜到。

console.log(Number('1'));   //1
console.log(Number('0'));   //0
console.log(Number(''));    //0
console.log(Number(''));    //0
console.log(Number(true));    //1
console.log(Number(false));    //0
console.log(Number(null));    //0
console.log(Number(undefined));   //NaN
console.log(Number('hello'));   //Nan
console.log(Number(null));    //0
console.log(Number(undefined));   //NaN
console.log(Number('hello'));   //Nan

2.3 转字符串

转字符串就不管是什么,直接加个''即可;

console.log(String(undefined));    //undefined
console.log(String(null));    //null

三. 引用类型转原始类型

3.1 引用类型转布尔

全部引用类型转布尔都是true,无论是空还是有属性。 image.png

3.2 引用类型转字符串

引用类型转字符串可以使用String(x)来表示其转化成字符串的值,实质上就是调用它们自己toString(x)方法,如下;

image.png

  • 对于一个对象{},它调用的是对象原型上的toString()方法(也可以用Object.prototype.toString()调用),所以会返回'[object' 和 class 和 ']'组成的字符串。
  • 对于一个数组[],它的构造函数上已经有了toString方法,就不会沿着原型链接着往上查找到对象原型上的方法了,就将里面每个元素用字符串方式输出。
  • 对于其它,其对象原型上有其相应的 toString() 方法,所以返回对应的字符串字面量。

3.3 引用类型转数字

image.png 对象转数字为NaN。

ToPrimitive

为什么当x是一个对象时,String(x)返回的是它的类型呢?而且这个与Object.prototype.toString(obj)的输出结果是一样的。

let obj = {
  a: 1,
  b: 2
}
console.log(String(obj));   //[object Object]
console.log(Object.prototype.toString(obj));    //[object Object]

那么就不得不提到当执行String(x)时,当x是一个对象时,V8 引擎会尝试将 x 转换为一个原始字符串值,这个过程涉及到 ToPrimitive 抽象操作(通常会在将对象转换为原始值(字符串,数字或布尔值)时用到)。它接收俩个参数,一个为需要转换的变量,一个为要转换成的类型(以ToPrimitive(x,type)为例),执行过程如下。

如果要转换成的类型type为String;

  1. 如果 x 是原始类型,若是直接返回
  2. 如果 x 是引用类型,则调用 toString(),得到原始类型就返回
  3. 如果得不到原始类型,则调用 valueOf(),得到原始类型就返回
  4. 否则报错

如果要转换成的类型type为Number;

  1. 如果 x 是原始类型,若是直接返回
  2. 如果得不到原始类型,则调用 valueOf(),得到原始类型就返回
  3. 如果 x 是引用类型,则调用 toString(),得到原始类型就返回
  4. 否则报错

前面已经聊过toString了,接下来我们聊一聊valueOf()valueOf() 是一个所有对象都继承自 Object.prototype 的方法,valueOf() 方法返回指定对象的原始值,如下。

var boolObj = new Boolean(true);
console.log(boolObj.valueOf()); // 输出: true

var numObj = new Number(10);
console.log(numObj.valueOf()); // 输出: 10

var strObj = new String("hello");
console.log(strObj.valueOf()); // 输出: "hello"

var dateObj = new Date();
console.log(dateObj.valueOf()); // 输出: 当前日期的毫秒表示

var arrObj = [1, 2, 3];
console.log(arrObj.valueOf()); // 输出: [1, 2, 3] (数组本身)

var funcObj = function () { return "Hello"; };
console.log(funcObj.valueOf()); // 输出: function() { return "Hello"; } (函数本身)

var obj = new Object();
console.log(obj.valueOf()); // 输出: {} (对象本身)

举个对象转数字类型的例子;

let obj = {
  a: 1,
  b: 2
}
console.log(Number(obj))

当执行这段代码时,调用Number(obj)方法想将 obj 对象转换为Number数字类型,于是v8就会自动调用ToPrimitive(obj,Number)这个方法,然后按照上述操作步骤,首先判断obj是否是原始类型,显然不是,因为想要转换的类型为Number所以首先调用valueOf(obj);

console.log(obj.valueOf());   //{ a: 1, b: 2 }

显然{ a: 1, b: 2 }不是一个原始类型,然后调用toString()

console.log({ a: 1, b: 2 }.toString());  // [object Object]

此时[object Object]就是一个字符串了,然后直接返回,再执行Number('[object Object]')

console.log(Number('[object Object]'));   // NaN

结果输出NaN,这也就是对象转数字的全过程了,当然其它对象转类型也可以以此类推。

四. 隐式类型转换的场景

隐式转换通常出现在四则运算以及判断语句中,如下;

  1. 四则运算 + - * / %
  2. 判断语句 if while == > < >= <= !=等

4.1 一元运算符 +

一元运算符会将其操作数转换为Number类型,如下;

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

数组转数字前面提到,就是使用ToPrimitive([],Number),显然数组不是原始类型,且想转数字,先[].valueOf()得到[]然后再[].toString()得到一个空字符串,再空字符串转NumberNumber('')结果为0;

console.log([].valueOf()); // []
console.log([].toString()); // ''
console.log(Number('')); //0

4.2 二元运算符 +

console.log('1' + 2);   // 12
console.log(2 + '1');   // 21

val1 + val2

左边元素为 lprim = ToPrimitive(val1),右边元素为 rprim = ToPrimitive(val2)

  • 如果 lprim 或者 rprim 是字符串,另一个值直接被 ToString()
  • 否则,返回对 ToNumber(lprim) 和 ToNumber(rprim) 应用加法运算的结果

ToString()(或ToNumber()):这个操作用于将一个值转换为一个数值。它不关心输入值是否是对象,而是直接将输入值(无论是原始值还是对象)转换为一个字符串(数值)。

  1. 如果x为原始值,则直接将它转换成想转换的类型,ToString(x)则转换为String,ToNumber(x)则转换为Number。
  2. 如果x是一个引用类型且要转换为原始值时(也就是它自己转换不了),则执行与前面的ToPrimitive类似的操作

4.3 [ ]==![ ]

我们先来看看关于==的官方文档。 image.png 然后我们再来看[]==![],首先有!会先将[]隐式转换为Boolean值,引用类型转换为布尔值都为true,然后再执行!true,变成[]==false,再看上述第七条,执行ToNumber(false)结果为0, 即[]==0,再看第九条,[]显然是一个对象,则变成ToPrimitive([],Number)

上面ToPrimitive执行过程: image.png

valueOf([])得到一个空字符串,然后字符串显然是一个原始类型,直接返回。

image.png 于是再将空字符串转换成数字0,则此时原式变为0 == 0,结果输出true。

好了,关于js中类型转换机制就介绍到这里了,如果觉得本篇文章对你有所帮助可以点点赞。

无字动图.gif