类型转换分为显示转换和隐式转换
显示类型转换
通过JS定义的方法将参数进行类型转换
- 比如使用String或者toString强制将参数转换为String类型
- 比如使用Object
- 比如使用parseInt、parseFloat强制转换为数字
console.log(String({a:'1'}))
console.log(Object("1"))
console.log(parseInt("123aaa"))
console.log(parseInt("aa123a"))
特别提一下下面三个转数字的方式
parseInt
parseInt(string, radix)解析一个字符串并返回指定基数的十进制数,radix是2-36之间的整数,表示被解析字符串的基数
1、如指定 16 表示被解析值是十六进制数,如果超出这个范围,将返回 NaN
2、指定 0 或未指定,基数将会根据字符串的值进行推算,规则如下:
string 以 “0x” 开头,将其余部分解析为十六进制的整数
string 以 0 开头,将其后的字符解析为八进制或十六进制的数字
string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数
parseInt('123', 5) // 将'123'看作 5 进制数,返回十进制数 38 => 1*5^2 + 2*5^1 + 3*5^0 = 38
parseInt 将 BigInt 转换为 Number,并在这个过程中失去了精度。这是因为拖尾的非数字值,包括 “n”,会被丢弃,但是使用一元运算符+会报错
一元运算符+
运算符+用于转换操作数为数字
- 对于一般能正常转换的数据,和Number()函数的输出值一致
function toNumber(val){
return +val
}
console.log(toNumber(null)) // 0
console.log(toNumber(undefined)) // NaN
console.log(toNumber(1)) // 1
console.log(toNumber('123abc')) // NaN
console.log(toNumber({})) // NaN
console.log(toNumber(true)) // 1
- 对于BigInt数据类型,运算符+会报错,Number()返回n前面的数字
- 对于Symbol类型,运算符+和Number()函数都会报错
位移
位移分为有符号位移和无符号位移,位移的特点是,移出的多余位(超出32位)将会被丢弃,并且位移始终会返回数字,不会返回NaN;如果位移0位那么就是将操作数转换为数字
无符号位移
将左操作数计算为无符号数,并将该数字的二进制表示形式移位为右操作数指定的位数,如果右操作数大于32则会先对32取模,比如是33那么相当于位移1位;向右移动的多余位被遗弃,零位从左移入,所以无符号位移的结果始终为非负数
function toNumber(val) {
return val >>> 0;
}
有符号位移
和无符号位移一样,但是左边的符号位保留
function toNumber(val) {
return val >> 0;
}
共同点
- 对于能正常转换的数据有符号位移和无符号位移是一样的
console.log(toNumber(null)) //0
console.log(toNumber(undefined))//0
console.log(toNumber(1))//1
console.log(toNumber('123abc'))//0
console.log(toNumber({}))//0
console.log(toNumber(true))//1
- 对于BigInt、Symbol数据类型都不能正常转换会抛出错误
- 对于最大安全数Number.MAX_SAFE_INTEGER两者输出的值有一些差别
console.log(Number.MAX_SAFE_INTEGER >> 0) // -1
console.log(Number.MAX_SAFE_INTEGER >>> 0) // 4294967295
解析:
1、将最大安全数转换成二进制为”11111111111111111111111111111111111111111111111111111″
2、超过32位被丢弃为”111111111111111111111111111111″
3、有所区别
- 无符号位移直接将第二步的值转换
console.log(parseInt('11111111111111111111111111111111',2)) // 4294967295
- 有符号位移需要减一取反得到原码
// 减1得到 11111111111111111111111111111110
// 取反得到00000000000000000000000000000001
// 符号位为最高位1所以得到-1
原码、反码、补码:
一个数可以分成符号位(0正1负)+ 真值,原码是我们正常想法写出来的二进制。由于计算机只能做加法,负数用单纯的二进制原码书写会出错,于是大家发明了反码(正数不变,负数符号位不变,真值部分取反);再后来由于+0, -0的争端,于是改进反码,变成补码(正数不变,负数符号位不变,真值部分取反,然后+1)。二进制前面的0都可以省略,所以总结来说:计算机里的负数都是用补码(符号位1,真值部分取反+1)表示的。
隐式类型转换
编译器自动完成类型转换,并且总是期望返回基本数据类型;会发生隐式类型转换一定是预期值与传入值不一致
会发生隐式类型转换的情况:
- 逻辑运算符!、while()、if()、三目运算符
- 二元运算符+
- 关系运算符,比如>、>=、<、<=、==
- 属性键遍历、for in
- 模板字符串
条件判断,逻辑运算符
都是往Boolean转,被转为布尔值为false的值:undefined、null、NaN、0、”、+0、-0
二元运算符+
1、如果一个操作数是字符串,那么会将另一个操作数转换成字符串
2、如果操作数是对象,那么会将对象转换成基本数据类型,这里涉及到对象类型的转换,后面细聊
3、如果是其他情况,两边操作数都会转换成数字,如果是非数字那么就转为NaN
4、对于Symbol类型会抛出异常,Symbol类型不能转换成数字类型或者字符串类型
5、对于BigInt,如果与字符串类型(会转换成数字类型)相加,会忽略后面的n,与数字类型(会转换成数字类型)相加报错
console.log(1 + "23"); // 123
console.log(1 + false); // 1
console.log("1" + false); // 1false
console.log("1" + 1n); // 11
console.log(false + true); // 1
console.log({} + 1n); // [object Object]1
console.log(true + 1n); // 出错
console.log(1 + 1n); // 出错
console.log('1' + Symbol('a')); // 出错
console.log(1 + Symbol('a')); // 出错
关系运算符==
如果两边类型不一样就会进行类型转换
对比流程:
1、先比较类型,如果类型一致就使用===比较大小
2、判断是否是null和undefined,是的话返回true
3、判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number
4、Boolean和其他类型比较,把 boolean 转为 number
5、Object和其他类型比较,将object转换为基础数据类型
对于特殊的NaN应该也会是先判断的吧,毕竟和谁都不等;如果有BigInt类型,判断类型不等应该就返回false,如果类型一样再判断是否相等;如果是Symbol类型也是都不等
对象的隐式转换
- 如果Symbol.toPrimitive方法存在,优先调用,对象内置方法,可以更改
- hint参数是引擎根据执行上下文决定的,默认是default,比如二元+,字符串和字符串可以加数字和数字可以加,所以就使用默认,通常对于内置对象,default一般处理成转换为number(优先调用valueOf方法),除了Date(优先调用toString方法)
- 如果是string,优先调用toString方法,比如调用调用window.alert(obj)、模板字符串
${obj}
、test[obj] = 123 - 如果是number,优先调用valueOf方法,比如一元+,位移,关系运算符- * /,关系运算符> <当一边操作数为数字时,Math.pow,String.prototype.slice等等
// 写个方法改变obj调用[Symbol.toPrimitive]方法 const obj = { [Symbol.toPrimitive](hint) { if(hint === "number"){ return 10; } if(hint === "string"){ return "hello" } return true } } console.log(`${obj}`);//hello console.log(+obj);//10 console.log(1+obj);//11
- 如果Symbol.toPrimitive方法不存在,如果期望是string那么先调用obj.toString(),返回的不是原始值,再调用obj.valueOf()
- 如果期望的是number,那么先调用obj.valueOf()返回的不是原始值,再调用obj.toString()
- 如果都返回的不是原始值则抛出错误
const obj = {
value: 10,
toString:function(){
return {};
},
valueOf:function(){
return {};
}
}
console.log(10 + obj);
- ===和!==不会触发隐式转换,两边都是对象是==和!=也不会触发隐式转换
console.log({} == {});// false
console.log([] == []);// false
const obj = {
name:"John",
age: 10,
toString:function(){
return this.name;
},
valueOf:function(){
return this.age;
}
}
console.log(`${obj}`); // John
// 问题1:如果想调用console.log(`${obj}`)返回10
console.log(+ obj); // 10
// 问题2:如果将valueOf方法改为return this,+obj返回什么
- 问题1:只需要将toString方法改为返回this
- 问题2:返回NaN,因为会调用toString方法
const obj = {
value: 10,
toString:function(){
return this.value + 10;
},
valueOf:function(){
return this.value;
}
}
obj[obj] = obj.value;
console.log(Object.keys(obj)); // [value,toString,valueOf,20];说明作为键时期望是string
console.log(`${obj}`);// 20
console.log(obj + 1); // 11
console.log(obj + "1");// 10,这里是调用[Symbol.toPrimitive](hint),采用默认值default,处理转换成number,优先调用valueOf方法
问题1:const val = [] == ![]
问题2:[+val, [] + 1] == [1,1] + []
问题3:[+val, [] + 1] == [1, '1']
问题4:[] + {}
问题5:{} + []
问题6:{} + {}
解析:
先理清楚[].valueof()、[].toString()、{}.valueOf()、{}.toString()
分别返回[]、””、{}、”[object Object]”
问题1:首先会转换![]返回false,根据==转换法则有Boolean时都转为number,false转number是0,[]转为number也是0(先调用valueof不是基础数据类型,再调用toString()返回””转为数字是0);所以最终val = true
问题2:+val 按照数字转是1,[] + 1转[]变为”然后+1变为字符串1,所以左边是[1,’1′]转为字符串是’1,1’;右边都按照字符串转[1,1]转为’1,1’,[]转为空字符串,所以最后也是’1,1′
问题三:左边转为[1, ‘1’],右边也是数组[1,’1′],两边都是数组将不再转换,引用地址不同最终返回false
问题四:比较简单返回'[object Object]’
问题五:会被解析成 {}; + [],也就是一个语句后面跟了一个 +[],+[]相当于 + ” ,结果为 0
问题六:NaN