1.前言
谈到类型转换, 那么我们先简要列一下js常见的数据类型: 七大原始数据类型为string, number, boolean, undefined, null, symbol, bigInt和引用数据类型Object
js的类型转换只有三种类型的转换: to string, to boolean, to number, 即原始数据类型{string, number, boolean, undefined, null} + 引用数据类型{object, ... } to→ {string, boolean, number}的类型转换.
而在这三种类型转换当中, 分为两大块: 显式类型转换和隐式类型转换. 显式类型转换是隐式类型转换的基础, 到后面你会发现, 隐式类型转换就是在操作符的作用下进行显式类型转换, 所以着重掌握显式类型转换的机制, 然后再记住哪些操作符会触发隐式类型转换即可。
2.定义+代码解析
1.显式类型转换
在了解隐式转换的规则前先来看看强制类型转换,强制类型转换主要是用Boolean()/String()/Number()将各类型的数据转换成布尔、字符串、数值型数据。
1.Boolean()函数
Boolean()函数默认是false,还有这几种情况:undefined,null,+0,-0,NaN,''(空字符串),其它全为true。注意Boolean([])=true,空数组转化为布尔值也是true,因为数组也是对象,凡是对象转化为Boolean都为true
//全部输出false
console.log(Boolean()) // false 默认
console.log(Boolean(0))
console.log(Boolean(undefined))
console.log(Boolean(null))
console.log(Boolean(+0))
console.log(Boolean(-0))
console.log(Boolean(NaN))
console.log(Boolean(''))
//下面全为true
console.log(Boolean(1)) //除0以外的数字
console.log(Boolean({}))
console.log(Boolean([]))
2.Number()函数
- null、''(空字符串)、[] ->0;
- fasle->0、true->1
- "number"->number (连续的数字字符串,去掉前面的0)
- "<16进制数>" -> <16 进制数> -> 10 十进制数
- 其它都为NaN
console.log(Number()); // 0 默认
console.log(Number(null)) //0
console.log(Number(false)) // 0
console.log(Number(true)) // 1
console.log(Number('123')) // 123
console.log(Number('-123')) // -123
console.log(Number('1.23')) //1.23
console.log(Number('000123')) //123
console.log(Number('-000123')) //-123 (去掉前面的0,保留了符号)
console.log(Number('0x17')) // 23 (默认转化为十进制)
console.log(Number('')) //0
console.log(Number(' ')) // 0
console.log(Number([])) // 0
console.log(Number(undefined)) // NaN
console.log(Number('111 222')); // NaN
console.log(Number('aaa')); // NaN
console.log(Number({})); //NaN
console.log(Number(NaN));//NaN
3.String()函数
最后一种强制类型转换方法 String() 是最简单的,因为它可把任何值转换成字符串。
console.log(String(undefined)) //undefined (字符串)
console.log(String([])) // ''(空字符串)
console.log(String({})) // [object Object] (对象的字符串形式)
console.log(String('')) // ''(空字符串)
console.log(String(null)) //null
console.log(String(true)) //true
console.log(String(123)) //123
console.log(String('123')) 123
4.对象 to {string, number, boolean}的 To Primitive类型转换
js的类型转换只有其他类型到{string, number, boolean}的类型转换, 其中对象到{string, number, boolean}的类型转换称作为to primitive类型转换, 即对象转原始数据类型。
1.to string:
- 调用对象的
toString()方法, 没有则去原型链上查找 -
- 如果toString返回值为原始值, 对返回值进行原始值 to string的类型转换 转换后的结果即为对象 to string的类型转换结果
- 如果toString返回值为对象, 那么将调用
<obj>的valueOf()方法 - 如果返回值为原始值, 对返回值进行原始值to string的类型转换, 转换后的结果即为对象 to string的类型转换结果
- 如果返回值为对象, 则报错 3. to number:
- 调用对象的
valueOf()方法, 没有则去原型链上查找 -
- 如果valueOf返回值为原始值, 对返回值进行原始值 to number的类型转换(2.2 节), 转换后的结果即为对象 to number的类型转换结果
- 如果valueOf返回值为对象, 那么将调用
<obj>的toString ()方法 - 如果返回值为原始值, 对返回值进行原始值to number的类型转换, 转换后的结果即为对象 to number的类型转换结果
- 如果返回值为对象, 则报错
2. to boolean:
关于对象转为布尔值的机制很简单, 一般情况下, 对象to boolean都是直接转换为true, 而且不会调用对象的[Symbol.toPrimitive], toString, valueOf 这三个方法.
2.隐式类型转换
隐式类型类型转换大多发生在操作符当中, 比如宽松相等操作符==两端的类型不同的时候, 会发生隐式类型转换。 而有时候, 某些函数的参数如果是对象, 也会触发对象的类型转换。
1.一元操作符
+val | -val 会默认调用 ToNumber 处理val。
如果val是对象类型,会先调用ToPrimitive()方法,执行的步骤是:
- 如果 val 是基本类型,就直接返回
- 否则,调用 valueOf 方法,如果返回一个原始值,则js将其值返回
- 否则,调用 toString 方法,如果返回一个原始值,则js将其值返回
- 否则,报类型错误
举个例子:
+['1']=1怎么来的:首先['1']判断为对象,调用valueOf()方法(['1'].valueOf()=['1']),还是对象,然后再调用toString()方法(['1'].toString()='1'),结果为字符串,将其返回,+'1'会自动转化为1。
console.log(+'1') // 1 ->Number('1')
console.log(+[]) //0 ->Number([])
console.log(+['1']) //1
console.log(+['1','2','3']) // NaN
console.log(+{}) //NaN
console.log(-'123') //-123
console.log(+'aaa') //NaN
2.二元运算符
如:val1 + val2
- v1 = Toprimitive(val1)
- v2 = Toprimitive(val2)
- 如果v1和v2其中一个为字符串,那么返回ToString(v1)和ToString(v2)的拼接结果,否则转4
- 返回 ToNumber(v1) 和 ToNumber(v2) 的运算结果
举个例子:
{}+[]='[object Object]'结果是个对象的字符串形式,如何转化的呢:首先Toprimitive({}):->{}->'[object Object]';Toprimitive([]):->[]->'';'[object Object]'+ '' ='[object Object]'
特殊情况: new Date()(也是对象) 会跳过步骤2,直接调用ToString()方法。本来new Date().valueOf会得到时间戳,是number类型,结果不会是字符串。说明跳过了valueOf()方法,比较特殊。
console.log([]+[]) // ''
console.log([]+0) // '0'
console.log({} + []) // '[object Object]'
console.log(new Date(2021,11,20) + 1) //'Mon Dec 20 2021 00:00:00 GMT+0800 (中国标准时间)1'
3.宽松相等运算符的隐式类型转换
两等号A == B的隐式类型转换规则其实不是很难, 个人总结的规则如下:
1.不会发生隐式类型转换的情形:(特殊情况)
- A和B均为{undefined, null}当中的一种,
A == B -> true - A为{undefined, null}当中的一种, B为{string, boolean, number, 对象}当中的一种,
A == B -> false - A和B对象均为对象, 则比较对象的地址是否相等 2.会发生隐式类型转换的情形, 以及类型转换规则:
- A 和 B均为{string, boolean, number}当中的一种, 且 A 和 B 的类型不一致. 那么,string和boolean都会先转为number, 然后再进行比较
- A 和 B 其中有一个为对象, 另一个为{string, boolean, number}中的一种. 此时一个为对象, 一个为原始值, 当两者进行相等比较的时候, 会对对象进行 to primitive 的类型转换
注意, 在隐式类型转换当中, 如果对象发生to primitive操作总是优先调用[Symbol.toPrimitive]. 如果没有定义则调用valueOf, 在valueOf返回对象的时候, 会继续调用toString方法。 - [Symbol.toPrimitive], toString, valueOf这三个方法在返回原始值的时候, 不会将原始值再转为其他的原始值类型, 而是直接返回结果, 作为对象的to primitive 类型转换结果
示例:
[] == ![]的结果是什么?(面试常问题,重点笔记)
解答: 结果为true
- 从左到右进行扫描,因为!优先级大于==,所以首先
![]触发类型转换, 将[]转为boolean,结果为true,取反为false。 - 然后判断
[] == false, 此时两端类型不一致, 因而触发数组对象[]的to primitive隐式类型转换, 调用[].prototype.valueOf()返回数组对象自身, 因而继续调用[].prototype.toString()返回'' - 然后判断
'' == false, 将''和false都转换为number, 再进行比较, 最终为0 == 0, 结果为true
3.小结
1.显式类型转换:
- to Boolean :除了这些undefined,null,0,+0,-0,NaN,''(空字符串)为false,其它全为true
- to Number :
- null、''(空字符串)、[] ->0;
- fasle->0、true->1
- "number"->number (连续的数字字符串,去掉前面的0)
- "<16进制数>" -> <16 进制数> -> 10 十进制数
- 其它都为NaN
- to String :可把任何值转换成字符串
- 对象 to {string, number, boolean}的 To Primitive类型转换:
- to boolean 全为true
- to string :先调用toString()方法,是原始值返回toString结果,否则调用valueOf()方法,是原始值返回toString结果,否则报错。
- to number: 先调用valueOf()方法,是原始值返回toNumber结果,否则调用toString()方法,是原始值返回to number结果,否则报错。
2.隐式类型转换
一元操作符会默认调用 ToNumber 处理该值、二元运算符会对对象分别调用ToPrimitive()方法、宽松运算符则是对有对象那一项进行调用ToPrimitive()方法。 ToPrimitive()方法的执行的步骤是:
- 如果 val 是基本类型,就直接返回
- 否则,调用 valueOf 方法,如果返回一个原始值,则js将其值返回
- 否则,调用 toString 方法,如果返回一个原始值,则js将其值返回
- 否则,报类型错误 所以只需要记住一些强制类型转换的特例,然后再记住ToPrimitive()方法的规则,就可以理清楚一些复杂的式子:[] == [] ->false、[]==![] ->true、if([])->true。你学废了吗?