这是我参与更文挑战的第 22 天,活动详情查看:更文挑战
JavaScript
是一种动态类型语言,也称弱类型语言,指的是它的变量没有类型限制,可以赋予任何类型的值。而在操作不同类型的值的时候,就会涉及到类型转换的操作(即将一种类型的值转换成另一种类型),比如将字符串'123'
转换成数字123
。
一般我们在明确知道自己想要转换的数据类型时,可以显式地进行类型转换操作,这种类型转换行为我们称为「强制类型转换」。
而在类型不明确时,可能会出现不同类型的值进行运算操作的情况。由于运算符对数据类型是有要求的,当类型不满足要求的时候,JavaScript 就会根据一定的规则自动地进行类型转换操作,这种行为称为「隐式类型转换」。
强制类型转换
强制转换通常使用Number()
、String()
和Boolean()
三个函数,手动将各种类型的值,分别转换成数字、字符串或者布尔值。
转数字类型
Number
函数如果不传参数调用,就返回 +0
,如果有传参数,就调用ToNumber(value)
得到结果。这个 ToNumber
是一个底层规范实现上的方法,并没有直接暴露出来。
根据规范 9.3所述,ToNumber
的结果表如下所示:
参数类型 | 结果 |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | 如果参数是 true,返回 1。参数为 false,返回 +0 |
Number | 返回与之相等的值 |
String | 如果可以被解析为数值,转换为相应的数值,不行的话就返回 NaN,详见例子 |
Object | 1. primValue = ToPrimitive(input, Number) 2. 返回 ToNumber(primValue) 。 |
简单示例如下所示:
// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324
Number('-Infinity') //-Infinity
Number('00000123') // 123
// 字符串:如果不可以被解析为数值,返回 NaN
Number(' 324abc ') // NaN
// parseInt 没有那么严格,会取前缀数字
parseInt(' 324abc ') // 324
// 它们都会忽略前缀和后缀空白符
parseInt('\t\v\r324abc\n') // 324
Number('\t\v\r324\n') // 324
// 空字符串转为0
Number('') // 0
// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0
// undefined:转成 NaN
Number(undefined) // NaN
// null:转成0
Number(null) // 0
复制代码
ToPromitive 方法
从结果表中可以看到,如果是用Number
转换对象类型,会先调用toPrimitive
方法。该方法本质是接受一个值,然后返回一个基本类型的值。参考规范9.1,规范8.12.8,语法如下所示:
ToPrimitive(input[, PreferredType])
复制代码
input
如果是基本类型( Undefined、Null、Boolean、Number、String),直接返回,否则进行转换。
PreferredType
选填,Number
或者 String
,如果input
是日期类型,则默认为String
,否则默认为Number
。
如果是
ToPrimitive(obj, Number)
,处理步骤如下:
- 如果
obj
为 基本类型,直接返回;- 否则,调用
valueOf
方法,如果返回一个原始值,则将其返回;- 否则,调用
toString
方法,如果返回一个原始值,则将其返回;- 否则,抛出一个类型错误异常。
如果是
ToPrimitive(obj, String)
,处理步骤如下:
- 如果
obj
为 基本类型,直接返回;- 否则,调用
toString
方法,如果返回一个原始值,则将其返回;- 否则,调用
valueOf
方法,如果返回一个原始值,则将其返回;- 否则,抛出一个类型错误异常。
默认情况下,对象的valueOf
方法返回对象本身,所以一般总是会调用toString
方法,而toString
方法返回对象的类型字符串(比如[object Object]
),数组的 toString 方法返回数组项拼接值,日期类型返回相应的日期字符串。
Number({}) // NaN
Number([5]) // 5
复制代码
可以改写对象的valueOf
跟toString
,从而控制该对象经过类型转换得到的值,如下所示:
let obj = {
valueOf: function () {
return 1;
},
toString: function () {
return 2;
}
};
Number(obj) // 1
复制代码
转字符串类型
使用String
方法将类型转换成字符串类型,参考规范 9.8,如果 String
函数不传参数,返回空字符串,如果有参数,调用 ToString(value)
,ToString
的结果表如下所示:
参数类型 | 结果 |
---|---|
Undefined | “undefined” |
Null | “null” |
Boolean | 如果参数是 true,返回 “true”。参数为 false,返回 “false” |
Number | 转化为相应的字符串 |
String | 返回与之相等的值 |
Object | 1. primValue = ToPrimitive(input, String) 2. 返回 ToString(primValue) |
简单示例如下所示:
String() // 空字符串
String(undefined) // undefined
String(null) // null
String(false) // false
String(true) // true
String(0) // 0
String(-0) // 0
String(NaN) // NaN
String(Infinity) // Infinity
String(-Infinity) // -Infinity
复制代码
对象类型转字符串类型,也是通过 ToPrimitive
函数进行了一层转化,具体转化规则见上文,优先调用对象的toString
,其次使用对象的valueOf
。
同样,可以通过改写对象的valueOf
跟toString
,控制该对象经过类型转换得到的值,如下所示:
let obj = {
valueOf: function () {
return 1;
},
toString: function () {
return 2;
}
};
String(obj) // "2"
复制代码
转布尔类型
使用Boolean
函数将类型转换成布尔类型,参考规范9.2,只有 6 种值可以被转换成 false,其他都会被转换成 true。
Boolean() // false
Boolean(false) // false
Boolean([])
// 只有以下6种值可以被转换为 false
Boolean(undefined) // false
Boolean(null) // false
Boolean(+0) // false
Boolean(-0) // false
Boolean(NaN) // false
Boolean("") // false
复制代码
所有对象(包括数组和函数)都转换为 true。对于包装对象也是如此。
console.log(Boolean(new Boolean(false))) // true
复制代码
隐式类型转换
当遇到以下三种情况时,JavaScript 会进行隐式类型转换。
- 不同类型的数据进行相互运算,如
1 + '1'
- 非布尔类型的数据求布尔值,如
if('123'){...}
- 非数字类型的值使用一元操作符(
+
和-
)
一般来说,操作预期的值是什么类型,就自动调用相应的类型转换函数进行类型转换。
'a' ? true : false; // 使用 Boolean() 对 'a' 进行转换
'5' - '2'; // 使用 Number() 进行转换
+'123'; // 使用 Number() 进行转换
复制代码
另外有两个特殊的操作要进行单独分析,分别是二元运算符+
和 非严格相等==
,它们的隐式转换规则比较复杂。
二元运算符+
参考规范 11.6.1 和 这篇文章,二元运算符+
的隐式转换规则如下:
当计算
value1 + value2
时:
lprim = ToPrimitive(value1)
rprim = ToPrimitive(value2)
- 如果
lprim
是字符串或者rprim
是字符串,那么返回ToString(lprim)
和ToString(rprim)
的拼接结果- 返回
ToNumber(lprim)
和ToNumber(rprim)
的运算结果
几个例子如下所示:
let obj1 = {
valueOf() {
return 1;
},
toString() {
return '2';
}
}
let obj2 = {
valueOf() {
return 1;
},
toString() {
return '2';
}
}
obj1 + obj2 === 2 // true
null + 1 === 1 // true
[] + [] === "" // true
console.log({} + []); // "[object Object]"
console.log({} + {}); // "[object Object][object Object]"
复制代码
==
非严格相等
参考规范 11.9.3,非严格相等的比较规则如下表所示:
被比较值 B | |||||||
---|---|---|---|---|---|---|---|
Undefined | Null | Number | String | Boolean | Object | ||
被比较值 A | Undefined | true |
true |
false |
false |
false |
false |
Null | true |
true |
false |
false |
false |
false |
|
Number | false |
false |
A === B |
A === ToNumber(B) |
A=== ToNumber(B) |
A== ToPrimitive(B) |
|
String | false |
false |
ToNumber(A) === B |
A === B |
ToNumber(A) === ToNumber(B) |
A== ToPrimitive(B) |
|
Boolean | false |
false |
ToNumber(A) === B |
ToNumber(A) === ToNumber(B) |
A === B |
ToNumber(A) == ToPrimitive(B) |
|
Object | false |
false |
ToPrimitive(A) == B |
ToPrimitive(A) == B |
ToPrimitive(A) == ToNumber(B) |
|
几个关键的点需要记忆:
-
Undefined
和Null
与自身或对方进行==
运算为true
,其余为false
; -
NaN
与任何数据(包括NaN
)进行==
运算都为false;
-
Boolean
类型与其它类型进行==
运算时,隐式转换成Number
进行比较; -
String
与Number
、Boolean
进行==
运算,转换成Number
进行比较; -
对象与其它类型比较的时候都进行
ToPrimitive
转换后进行比较。
false == undefined // false
false == [] // true
[] == ![] // true
// 以下都为 true
false == "0"
[] == 0
"" == [null]
0 == "\n"
[] == 0
+0 == -0
0 == !!null;
0 == !!undefined;
// 字符串相关比较,都是对象的时候比较引用值
const string1 = "hello";
const string2 = String("hello");
const string3 = new String("hello");
const string4 = new String("hello");
console.log(string1 == string2); // true
console.log(string1 == string3); // true
console.log(string2 == string3); // true
console.log(string3 == string4); // false
// 由于布尔值会被转换成数字进行比较
// 所以不建议这样写 if 条件
if (a == true) {}
// 建议
if (a) {}
// 或
if (!!a) {}
复制代码
由于隐式转换是自动进行的,不容易被察觉,容易出现异常,所以建议在明确类型的使用场景下,显式地进行类型转换。
顺带提一下严格相等
===
与Object.is
不同点,
+0 === -0 // true
,Object.is(+0, -0) // false
NaN === NaN // false
,Object.is(NaN, NaN) // true