1、为什么 JS 会有数据类型转换?
先从一个最本质的特点说起 👇
(1) JavaScript 是一门「动态类型语言」
js
var i = 1;
i = "zhangsan";
console.log(i);
在 JS 中:
- 变量没有固定类型
- 类型是在运行时才确定的
这和 Java / TypeScript 完全不同。
(2) 运算符「有类型预期」
虽然变量没有类型,但运算符是有要求的 。虽然是两个字符串相减,但是依然得到数值 1,原因就在于 JavaScript 将运算子自动转为了数值。
所以接下来就来看一下 JavaScript 中如何进行数据类型转换。
js
'4' - '3' // 1
减法运算符期望的是「数字」,于是 JS 会在背后偷偷做一件事:
js
'4' → 4
'3' → 3
这就是数据类型转换存在的根本原因
2、显式转换(你自己动手)
强制转换主要指使用Number()、String()和Boolean()三个函数,手动将各种类型的值,分别转换成数字、字符串或者布尔值。
2.1 Number():把一切变成数字
(1)原始类型
原始类型值的转换规则如下:
js
Number('') // 0
Number('123') // 123
Number('zhangsan') // NaN
Number('123?') // NaN
Number(true) // 1
Number(false) // 0
Number(undefined) // NaN
Number(null) // 0
⚠️ 注意:Number() 非常严格 , parseInt能转多少转多少,Number不纯就直接 NaN
js
parseInt('123abc') // 123
Number('123abc') // NaN
(2)对象转换规则(重点)
简单的规则是,Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组。
js
Number({n: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([6]) // 6
内部其实走了三步规则:
- 先调用对象自身的
valueOf()方法。如果返回原始类型的值,则直接对该值使用Number函数,不在进行后续操作。 - 如果
valueOf方法返回的还是对象,则改为调用对象的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续操作。 - 如果
toString方法返回的仍是对象,就报错。
js
var obj = { name: 'zhangsan' };
obj.valueOf(); // { name: 'zhangsan' }
obj.toString(); // "[object Object]"
Number(obj); // NaN
// 等价于
if(typeof obj.valueOf() === 'object') {
Number(obj.toString());
} else {
Number(obj.valueOf());
}
上述代码,Number函数将obj对象转为数值,首先调用obj.valueOf方法, 结果返回对象本身;于是,继续调用obj.toString方法,这时返回字符串[object Object],对这个字符串使用Number函数,得到NaN。
如果toString方法返回的仍不是原始类型的值,结果就会报错
js
const obj = {
valueOf() {
return {}
},
toString() {
return {}
}
}
Number(obj);
// TypeError: Cannot convert object to primitive value
从上述可以看出,valueOf和toString方法,是可以自定义的。
js
// 验证会调用valueOf
const obj = {
valueOf() {
return 10;
}
}
Number(obj); // 10
// 验证会调用toString
const obj2 = {
toString() {
return 20
}
}
Number(obj2); // 20
// 验证valueOf方法先于toString方法
const obj3 = {
valueOf() {
return 10
},
toString() {
return 20
}
}
Number(obj3); // 10
数组为什么不一样?
js
Number([5]) // 5
Number([1,2]) // NaN
原因拆解:
js
[5].valueOf() // [5]
[5].toString() // "5"
Number("5") // 5
[1,2].toString() // "1,2"
Number("1,2") // NaN
执行顺序永远是:
valueOf方法 优先于 toString方法
2.2 String():转成字符串
(1)原始类型值
js
String(123); // "123"
String(true); // "true"
String('test'); // "test"
String(undefined); // "undefined"
String(null); // "null"
对象规则
String方法的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。
js
String({a:1}) // "[object Object]"
String([1,2]) // "1,2"
String方法背后的转换规则,与Number方法基本相同,只是互换了valueOf方法和toString方法的执行顺序。
- 先调用自身
toString方法,如果返回原始类型的值,则对该值使用String函数,不再进行后续操作。 - 如果
toString方法返回的还是对象,则调用对象的valueOf方法,如果valueOf方法返回原始类型的值,则对该值使用String函数,不再进行后续操作。 - 如果
valueOf方法返回的仍是对象,就报错。
例如:
js
String({ name:'zhangsan' }); // "[object Object]"
// 等价于
String({ name:'zhangsan' }.toString()) // "[object Object]"
如果toString和valueOf方法,返回的都是对象就报错。
js
const obj = {
toString() {
console.log('1');
return {}
},
valueOf() {
console.log('2');
return {}
}
};
String(obj) // TypeError: Cannot convert object to primitxive value
从上述可以看出,toString和valueOf方法,是可以自定义的。
js
const obj = {
toString() {
console.log('1');
// return {}
return 10;
},
valueOf() {
console.log('2');
return 20;
}
};
// toString方法如果返回10则输出'10'
// toString返回如果返回{},则继续执行valueOf方法,输出'20'
String(obj);
2.3 Boolean():最简单,但最容易踩坑
Boolean()函数可以将任意类型的值转为布尔值。它的转换规则比较简单,只有 5 个值是 false ,其他的值全部为true:
js
false
0
''
null
undefined
NaN
js
Boolean({}) // true
Boolean([]) // true
Boolean(false) // false
⚠️ 所有对象的布尔值,永远是 true
4、隐式转换
(1) 自动转布尔(if 判断)
js
if ('abc') {
console.log('Hello');
}
等价于:
js
if (Boolean('abc')) {}
快速写法:
js
!!'abc' // true
(2)自动转字符串(+ 号)
js
'5' + 1 // "51"
'5' + true // "5true"
'5' + {} // "5[object Object]"
核心规则:
只要有字符串,+ 就变成拼接
(3)自动转数字(- * /)
js
'5' - '2' // 3
'5' * [] // 0
'5' * [2] // 10
'5' * [1,2] // NaN
拆解 [] 的例子:
js
[].toString() // ""
Number("") // 0
(4)一元运算符 +
js
+'abc' // NaN
+true // 1
+false // 0
JavaScript 的类型转换分为 显式转换 和隐式转换。
- 显式转换 :是开发者主动调用
Number、String、Boolean等方法; - 隐式转换:发生在运算或条件判断中,由 JS 根据上下文自动完成;
- 转换本质:根据运算符的预期类型,调用对应的转换规则。