首先来看一看一些比较奇特的隐式转换
10 / '2' // 5
1 + true // 2
true === 1 // false
false === 0 // false
true + true === 2 // true
false + false === 0 // true
{} + [] === 0 // true
[] + {} === 0 // false
看到这些,你肯定会感觉到很奇怪,为什么一个数字除一个字符串得到了一个数字,为什么true+true===2,可是true===1却是false,为什么{}+[]===0为真,可是[]+{}===0却为假。
要讲隐式转换,不可避免先要知道JavaScript中的类型。
JS中有两大类型,分别是简单数据类型,复杂数据类型。
- 简单数据类型包含7种类型Symbol、null、undefined、number、string、boolean、BigInt
- 复杂数据类型就是Object,而数组,函数等都属于Object里的子类型。
通常我们判断元素类型是使用typeof操作符
typeof Symbol() === 'symbol' // true
typeof undefined === 'undefined' // true
typeof true === 'boolean' // true
typeof 1 === 'number' // true
typeof '1' === 'string' // true
typeof 111n === 'BigInt' //true
typeof { age: 20 } === 'object' // true
// 但还有一个例外
typeof null === 'object' // true
这是因为js中早期的bug,一直都存在null, js采取32位存值的方式,前三位的代表是类型 而000代表object null前三位也是 000 所以js判断null为object
手写一个判断类型的函数
能区分Object子类型,同时把null这个错误给改正过来。
const getTypeof =(val) =>{
var ans = typeof val;//除了function 之外的对象,除了null 外的简单数据类型
if(nus ==='object'){
//简单数据类型的 null
//复杂数据类型 细化
if(val===null){
ans='null'
}else if(Array.isArray(val)){ //数组判断新方法
ans='array'
}else{
//函数 数组 之外的对象的子类型
ans=Object.prototype.toString.call(val).slice(8,-1);
}
}
return ans;
}
所有的隐式转换都是基于强制类型转换的,所以我们先了解一下什么是类型之间的强制转换
ToNumber强制转换Number型
- Number简单数据类型转换
Number("1") // 1
Number("1,") // NaN
Number("") // 0
Number(false) // 0
Number(true) // 1
Number(null) // 0
Number(undefined) // NaN
Number(Symbol('s')) // TypeError...
-
复杂数据类型转换
- 对象在转number之前,会先转换为基础类型,再转换为number类型,这个过程称为ToPrimitive。
- ToPrimitive过程先检查对象是否存在valueOf方法,如果存在并且valueOf返回基本类型的值,则使用该值进行强制类型转换,如果没有,则使用toString方法返回的值进行强制类型转换。
var arr = [1, 2,3] Number(arr)// NaN //因为没有valueOf方法,使用toString方法返回的值进行强制类型转换,arr.toString()等于"1,2,3",强制转换后为NaN arr.valueOf = function() { return '123' } Number(arr) // '123' 强制转换成 123 arr.toString = function() { return '123' } Number(arr) //'123' 强制转换成 123 var obj = {} Number(obj) // NaN //因为没有valueOf方法,使用toString方法返回的值进行强制类型转换,obj.toString()等于'[object Object]',强制转换后为NaN var obj = { valueOf: function () { return '123' } } Number(obj2) // 123 //存在valueOf方法,返回'123','123'强制转换成123
ToString强制转换String类型
- String简单数据类型转换
String(1) // "1"
String(1n) // "1"
String(false) // "false"
String(true) // "true"
String(null) // "null"
String(undefined) // "undefined"
String(Symbol('s')) // "Symbol(s)"
- String复杂数据类型转换
- 调用该类型原型上的toString方法
String({ a: 1 }) // "[object Object]" 相当于 var obj ={a:1} obj.toString() //"[object Object]" var arr = [1,2,2]; arr.toString() // "1,2,3" String(arr) // "1,2,3"
重点要来了,JavaScript中是如何进行隐式转换的?
-
操作符+两边的隐式转换规则
如果其中一个操作数是字符串;或者其中一个操作数是对象,且可以通过ToPrimitive操作转换为字符串,则执行字符串连接操作,其他情况执行加法操作。
x + y => if (type x === string || type y === string ) return join(x, y) //满足其中一个操作数是字符串,采用连接操作 => if (type x === object && type ToPrimitive(x) === string) return join(x, y) => if (type y === object && type ToPrimitive(y) === string) return join(x, y) //满足其中一个操作数是对象,且可以通过ToPrimitive操作转换为字符串,采用连接操作。 => else return add(x, y) //其他情况采取加法操作对于执行加法操作的情况,如果操作数有一边不是number,则执行ToNumber操作,将操作数转换为数字类型。
[1, 2] + {} // "1,2[object Object]" /* 因为[1, 2]和{}均不是字符串,但是[1, 2]和{}均可以通过ToPrimitive操作 但是[1, 2]和{}均可以通过ToPrimitive操作转换为字符串 所以这里执行字符串连接操作,根据ToPrimitive的规则 [1, 2].valueOf()的值不是基础类型,所以我们使用[1, 2].toString()的值 这时候就变成了 "1,2" + {} 而{}也可以通过ToPrimitive操作转换为"[object Object]" 所以最后的结果是"1,2[object Object]" */ var obj={ valueOf:function(){return 1} } true + obj // 2 /* 因为true和obj都不是,而obj为一个对象,且obj不能通过ToPrimitive转换为字符串,所以采取加法操作。 true先执行强制转化 1 obj先进行ToNumber,obj为对象,进行ToPrimitive操作,obj中有valueOf方法,返回值,obj转换为1 最后1 + 1 得到2 */
不过这里有一个坑,那就是{} + [] === 0,这个坑主要在于编译器并没有将{}看成一个对象而且代码块。
{} + [] === 0
/*
首先{}编译器把它看成代码块,代码块不会返回值
因为[]为对象,且[]通过ToPrimitive转换为'',为字符串,采取加法操作,然后在ToNumber转化为0。
{} + []得到0
*/
-
操作符==两边的隐式转换规则
==操作符被称为抽象相等,一般我们会建议禁止在业务代码中使用抽象相等。
但有时候用起来却很方便,比如下拉框选项中即使我们拉取的数据是number类型,在onChange回调中value的值却是字符串,这时候使用抽象相等就挺舒服的。
规范
-
对于数字和字符串的抽象比较,将字符串进行ToNumber操作后再进行比较
-
对于布尔值和其他类型的比较,将其布尔类型进行ToNumber操作后再进行比较
-
对于对象和基础类型的比较,将对象进行ToPrimitive操作后在进行比较
-
对象之间的比较,引用同一个对象则为true,否则为false
true == '1' //true /* 采取第二条规范,布尔值比较,先将true进行ToNumber操作得到1 然后在根据第一条规范,将'1'ToNumber转换为1 最后就是1 == 1 输出true */ var obj = { valueOf: function() { return '1' } } true == obj // true /* 先采取第二条规范,true转换为1 然后采取第三条规范,obj进行ToPrimitive操作,有 ,返回值为'1' 最后采取第一条规范,将'1'ToNumber转换为1 得到1 == 1 true */ [] == ![] // true /* 首先![]对[]进行强制布尔转换,也就是[]==false 然后采取第二条规范,false转换为0 然后采取第三条规范,[]进行ToPrimitive操作,没有valueOf方法,采取toString得到'' 最后采取第一条规范,将''ToNumber转换为0 即 0 == 0 true */ -