js基础里面有关类型转换的文章其实都已经说烂了,但自己仍然模棱两可,尤其是一些奇怪的转化结果,有时候想不起来其中的原理,一知半解,没办法,一些基础还是需要多巩固。JS里面的类型转换一般讨论的是布尔类型、字符串、以及数值类型的转换,原始值之间的转换规则不多,涉及的更多的是引用类型。所以本篇会介绍基本的转换规则,以及JS里类型转换的原理——
ToPrimitive抽象操作,并稍稍介绍了宽松等价==里面的比较规则。
本篇文章主要是阅读了霖呆呆的文章《从206个console.log()完全弄懂数据类型转换的前世今生》上和下两篇文章后自己再加以总结而来,
ToPrimitive的原理同时参考了《你不知道的JS》(中卷),参考文章链接已放在文末。
1. 原始类型转换成布尔值
使用Boolean()进行类型转换
这个的转换规则最简单,Boolean强制转换规则:
| false、undefined、null、+0、-0、NaN、"" | false |
|---|---|
| 除了上面的情况 | true |
Boolean(false); // false
Boolean(undefined); // false
Boolean(null); // false
Boolean(0); // false
Boolean(-0); // false
Boolean(NaN); // false
Boolean(""); // false
Boolean(-1); // true
......
2. 原始类型转换成字符串String
使用String()进行类型转换
ECMAScript 有 5 种原始类型(primitive type),即 Undefined、Null、Boolean、Number 和 String。ES6中还包括Symbol 。
// 原始类型:
String(undefined); // "undefined"
String(null); // "null"
String(true); // "true"
String(false); // "false"
String(3); // "3"
String(NaN); // "NaN"
String('abc'); // "abc"
String(Symbol(1)); // "Symbol(1)"
3. 原始类型转换成数字Number
3.1 使用Number()进行类型转换
先来看原始类型:
Number(undefined); // NaN
Number(null); // 0
Number(true); // 1
Number(false); // 0
Number(3); // 3
Number(NaN); // NaN
Number(''); // 0
Number('abc'); // NaN
Number(Symbol); // NaN
Number(Symbol(1)); // 报错,Uncaught TypeError: Cannot convert a Symbol value to a number
规律:
- 纯数字的字符串,会被转为相应的数字
null转为0- Boolean类型转换为对应的1和0
Symbol会报错- 其它的基本类型,包括非纯数字的字符串、
NaN,undefined都会被转为NaN
引用类型:
3.2使用parseInt()、parseFloat()进行类型转换
解释:
-
parseInt() 函数可解析一个字符串,并返回一个整数。
-
parseFloat() 函数可解析一个字符串,并返回一个浮点数。
解析规则:
parseInt()、parseFloat()都有两个参数,一个string,一个是进制数radix,默认为10进制
- 在没有指定radix或者radix为0的情况下,parseInt会按十进制进行转换,如:parseInt(3)
- 以“0x”开头,parseInt会按十六进制进行转换;以0开头时ES5规定按照10进制解析
- 如果都是字母, 返回:NaN
- 如果都是数字,则返回整数
- 如果字母和数字都存在
- 以数字开头,则取截止到第一个字母出现之前的所有数字进行转换
- 如果参数“string”,以字母开头,直接返回NaN (10进制中字母不是一个有效的的表示)
- string头尾部空格将被自动除去
- 如果解析的不是字符串,则将其转换为字符串再解析
- 如果radix不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回NaN。如果第二个参数是0、undefined和null则直接忽略。
- 如果字符串包含对于指定进制无意义的字符,则返回
NaN。 - 自动转为科学计数法的数字,parseInt会将科学计数法的表示方法视为字符串
- 注意转换number类型的时候小数点也是识别不了的,会当作其他字母处理
parseInt(3); // 3
parseInt("0x11"); // 17 ----0x开头16进制
parseInt('abc'); // NaN ----都是字母
parseInt(NaN); // NaN ----非字符串转换成字符串再解析
parseInt(false); // NaN
parseInt('10'); // 10
parseInt("011w"); // 11, ----以数字开头截取到w前面
parseInt("w11"); // NaN ----以字母开头直接返回NaN
parseInt(11, 2); // 3 ----指定radix,按进制返回
parseInt(011, 2); // NaN ----011转换成string是9,而9对于二进制是非法字符
parseInt('546', 2) // NaN ----5、4、6对于二进制是非法字符
parseInt('011'); // 11 ----目前chrome、firefox、ie中的结果都是11,若想用8进制解析,请加上第二个参数radix
parseInt(011); // 9 ----跟上一行不同,011是Number类型,011转换成string是9
parseInt(1.11, 10); // 1
parseInt(" 011 ", 2); // 3 ----头尾部空格将被自动除去
parseInt('10', 37) // NaN ----radix的值范围在2-36之间,超出这个范围,则返回NaN
parseInt('10', 1) // NaN
parseInt('10', 0) // 10 ----radix参数是0、undefined和null则直接忽略
parseInt(1000000000000000000000.5) // 1, 等同parseInt('1e+21')
console.log(parseInt("1.23a12")) // 1 ---- 返回整数
parseInt(".23"); // NaN
parseInt(1.5); // 1 ---- 截取到小数点之前,所以为1,不存在四舍五入
parseFloat与parseInt类似,但也有不同
与parseInt()不同的是,parseFloat()可以将字符串转换成浮点数;但同时,parseFloat()只接受一个参数,且仅能处理10进制字符串。 (1)字符串中的第一个小数点是有效的,而第二个小数点就是无效的了,因此它后面的字符串将被忽略。 (2)如果字符串包含的是一个可解析为整数的数(没有小数点,或者小数点后面都是零),parseFloat()会返回整数。
parseFloat(3); // 3
parseFloat("0x11"); // 0 ----与parseInt不同,不识别十六进制,所以只截取到x前面的0
parseFloat('abc'); // NaN ----都是字母
parseFloat(NaN); // NaN ----非字符串转换成字符串再解析
parseFloat(false); // NaN
parseFloat('10'); // 10
parseFloat("011w"); // 11, ----以数字开头截取到w前面
parseFloat("w11"); // NaN ----以字母开头直接返回NaN
parseFloat(11, 2); // 11 ----忽略第二个参数
parseFloat(011, 2); // 9 ----忽略第二个参数
parseFloat(1.11, 10); // 1.11
parseFloat(" 011 "); // 11 ----头尾部空格将被自动除去
parseFloat(1000000000000000000000.5) // 1e+21, 等同parseInt('1e+21')
console.log(parseFloat("1.23a12")) // 1.23
parseFloat(".23"); // 0.23
4. 原始类型转对象Object
使用Object()
先来看原始类型,原始类型使用Object()来转换类型,有包装对象的会返回它们的包装对象,没有的如undefined和null会返回空对象如:
// 原始类型:
Object(undefined); // {}
Object(null); // {}
Object(true); // Boolean {true}
Object(false); // Boolean {false}
Object(3); // Number {3}
Object(NaN); // Number {NaN}
Object('abc'); // String {"abc"}
Object(Symbol(1)); // Symbol {Symbol(1)}
Object(10n); // BigInt {10n}
5. 对象转布尔值
还是按之前说的转布尔值的规则,除了false、undefined、null、+0、-0、NaN、""以外,其他数据转换成布尔值都是true;
所以,所有对象(包括数组和函数)都转换为 true。
6. 对象转字符串
所有对象除了null、undefined以外的任何值都可以调用toString()方法,通常情况下它的返回结果和String一样
使用toString
在来看对象转字符串前,可以先看看原始对象的使用:
// 原始类型:
undefined.toString(); // 报错
null.toString(); // 报错
true.toString(); // "true"
false.toString(); // "false"
3.toString(); // 报错,因为小数点属于数字类型
3.1.toString(); // "3.1"
(3).toString(); // "3"
NaN.toString(); // "NaN"
'abc'.toString(); // "abc"
Symbol(1).toString(); // "Symbol(1)"
- 我们常容易搞混的就是
String和toString,这两者到底有什么区别呢?String是一个类似于Function这样的对象,它既可以当成对象来用,用它上面的静态方法,也可以当成一个构造函数来用,创建一个String对象- 而
toString是除了null、undefined之外的数据类型都有的方法,通常情况下它的返回结果和String一样。
引用类型的转化
toString的转化规则:
- 数组的
toString方法是将每一项转换为字符串然后再用","连接 - 普通的对象(比如
{name: 'obj'}这种)转为字符串都会变为"[object Object]" 函数(class)、正则会被转为源代码字符串日期会被转为本地时区的日期字符串- 原始值的包装对象调用
toString会返回原始值的字符串
[].toString(); // ""
[1, 3].toString(); // "1,3"
({}).toString(); // "[object Object]"
({name: 'obj'}).toString(); // "[object Object]"
(function() {console.log(1);}).toString(); // "function() {console.log(1);}"
new Date().toString(); // "Sat Apr 11 2020 17:37:33 GMT+0800 (中国标准时间)"
Number(2).toString(); // "2"
Number.toString(); // function Number() { [native code] }" ----Number未调用时属于一个构造函数,返回源代码字符串
['', ''].toString(); // ","
[' ', ' '].toString(); // " , " ----注意是有空格的
new Map().toString(); // "[object Map]" ----Map本身没有toString的方法,所以调用的是Object.prototype.toString方法,返回的是类型,toString是通过继承Object的
new Set().toString(); // "[object Set]" ---- 同上
使用String
前面第2条已经说过原始对象使用String的方式了,下面单看一下各种对象形式:结果跟toString相同
String([]); // ""
String([1, 3]); // "1,3"
String({name: 'obj'}); // "[object Object]"
String(function() {console.log(1);}); // "function() {console.log(1);}"
String(new Date()); // "Sat Apr 11 2020 17:37:33 GMT+0800 (中国标准时间)"
String(Number(2)); // "2"
String(['', '']); // ","
String([' ', ' ']); // " , " ----注意是有空格的
String(new Map()); // "[object Map]"
String(new Set()); // "[object Set]"
7. 对象使用valueOf转基本类型(主要是数字)
valueOf 是对象的一个方法,它的作用是把对象转换成一个基本数据的值
基本数据类型调用valueOf()
// 原始类型:
undefined.valueOf(); // 报错
null.valueOf(); // 报错
true.valueOf(); // true
false.valueOf(); // false
3.valueOf(); // 报错,因为小数点属于数字类型
3.1.valueOf(); // 3.1
(3).valueOf(); // 3
NaN.valueOf(); // NaN
'abc'.valueOf(); // "abc"
Symbol(1).valueOf(); // Symbol(1)
引用类型对象调用valueOf()
valueOf的使用规则:
- 非日期对象的其它引用类型调用
valueOf()默认是返回它本身 - 而日期对象会返回一个
1970 年 1 月 1 日以来的毫秒数
[].valueOf(); // []
{}.valueOf(); // {}
(function () {}).valueOf(); // ƒ () {}
/(\[|\])/g.valueOf() // /(\[|\])/g
new Date().valueOf() // 1586602681789
8. 对象转数字
仍然使用Number这个函数,Number的执行过程会先调用valueOf()后调用toString()
Number([]); // 0 ----[]调用valueOf返回本身[],调用toString()返回空字符串,最后再将空字符串""转为数字0返回
Number([1, 3]); // NaN ----valueOf返回本身[1,3],toString再转为"1,3",非纯数字的字符串使用Number返回NaN
Number({name: 'obj'}); // NaN ----valueOf返回本身,toString再转为"[object Object]",非纯数字的字符串使用Number返回NaN
Number(function() {console.log(1);}); // NaN ----同上
Number(new Date()); // 1586773492674 ----日期对象使用valueOf返回毫秒数,使用Number转换数字结果为一个数字
Number(Number(2)); // 2
Number(['', '']); // NaN
Number([' ', ' ']); // NaN
Number([0]); // 0 ---- 因为toString返回的为'0',使用Number结果为0
Number(new Map()); // NaN
Number(new Set()); // NaN
Number(Symbol(1)); // 报错
总结规则:
-
如果对象具有 valueOf 方法,且返回一个原始值,则 JavaScript 将这个原始值转换为数字并返回这个数字
-
否则,如果对象具有 toString 方法,且返回一个原始值,则 JavaScript 将其转换并返回。
-
否则,JavaScript 抛出一个类型错误异常。
9. 强制转化的抽象操作--toPrimitive()
在进入toPrimitive这个话题之前,需要先科普一下,js内部用于实现类型转换的主要4个函数:
- ToPrimitive ( input [ , PreferredType ] )
- ToBoolean ( argument )
- ToNumber ( argument )
- ToString ( argument )
不过需要注意,这里说的ToString跟上文不同,指的是js引擎内部使用的函数,而不是定义在对象的那个函数。
上文说过的String和Number种转换方式,String是先调用toString再调用valueOf,Number是先调用valueOf再调用toString。实际上这些规则都是遵循JS的一套内部转换规则:toPrimitive()的执行规则。
下面来看一下这个神秘的toPrimitive()函数,该函数的形式如下:
toPrimitive(input,preferedType?)
第一个参数input是需要转换的值,第二个是可选参数表示需要转换成的类型(主要包括非object的那几种原始类型);PreferredType参数要么不传入,要么是Number 或 String
下面看一下这个函数的主要调用规则:
- 是否存在preferedType参数,
- 如果存在,再看是Number还是String;
- 如果不存在,判断input,
- 如果传入的是日期对象,则preferedType默认为Number类型,
- 否则preferedType为String
- 根据preferedType判断
- String,继续判断是基本类型还是引用类型
- 基本类型 ---- 直接返回
- 引用类型,则调用toString,判断结果
- 结果为基本类型:---- 直接返回
- 结果为引用类型,则调用valueOf,继续判断结果
- 结果为基本类型:---- 直接返回
- 结果仍然为引用类型,抛出异常
- Number
- 基本类型 ---- 直接返回
- 引用类型 ,调用valueOf,判断结果
- 结果为基本类型:---- 直接返回
- 结果为引用类型,则调用toString,继续判断结果
- 结果为基本类型:---- 直接返回
- 结果仍然为引用类型,抛出异常
- String,继续判断是基本类型还是引用类型
当使用Number或者String时,会首先调用toPrimitive这个抽象操作转化成原始类型(或异常),再进行最后的Number或String来处理这个值。
10. ==中的类型转换
先抛出一段概念,使用==进行比较时,会有下面的转换规则:
- 等号两边的类型是否相同,相同时则直接进行比较 (当然此例存在例外,
NaN永远不等于它自己,+0和-0是相等) - 等号两边的类型不同
- 都是基本类型
- String与Number比较,会将string转换为Number以后进行比较
- 任何东西与Boolean,将boolean转换为Number再进行比较 , (所以不要在任何情况下,使用
== true或== false) - null与undefined,一方如果为null或undefined,另一方只要也为null或undefined,就相等
- 有一边是引用类型
- String或Number与对象比较,将对象转为对应的String或Number再比较
- Boolean与对象,同前面,任何东西与Boolean,将boolean转换为Number再进行比较
- 两边都是引用类型,判断它们是不是指向同一个对象
- 都是基本类型
42 == '42'; // true , ---- number与string
42 == true; // false, ---- number与boolean
"42" == true; // false, ---- string与boolean ,true转化为number为1,变成string与number比较,"42"转为number类型42,再比较
"42" == false; // false, ---- 理由同上
null == undefined; // true
({a:1}) == ({a:1}); // false
[] == []; // false
[] == ![]; // true ---- 这里的![]被执行,转换成了Boolean类型false,boolean类型被转为number0;现在变成了一边是对象,一边是number;[]使用ToPrimitive转换为"",Number("")为0;最后相等
Tips:
- 可以使用
if(a == null) {}来判断null或者undefined;这比if (a === undefined || a === null) {}其实要合适
参考文章: