2024-08-27 10:14:31 +08:00

531 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 数据类型的转换
## 经典真题
- *JavaScript* 中如何进行数据类型的转换?
## 数据类型转换介绍
*JavaScript* 是一种动态类型语言,变量没有类型限制,可以随时赋予任意值。
```js
var x = y ? 1 : 'a';
```
上面代码中,变量`x`到底是数值还是字符串,取决于另一个变量`y`的值。`y``true`时,`x`是一个数值;`y``false`时,`x`是一个字符串。这意味着,`x`的类型没法在编译阶段就知道,必须等到运行时才能知道。
虽然变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的。如果运算符发现,运算子的类型与预期不符,就会自动转换类型。比如,减法运算符预期左右两侧的运算子应该是数值,如果不是,就会自动将它们转为数值。
```javascript
'4' - '3' // 1
```
上面代码中,虽然是两个字符串相减,但是依然得到数值 `1`,原因就在于 *JavaScript* 将运算子自动转为了数值。
所以接下来我们就来看一下 *JavaScript* 中如何进行数据类型转换。
## 强制转换(显式转换)
强制转换主要指使用`Number()``String()``Boolean()`三个函数,手动将各种类型的值,分别转换成数字、字符串或者布尔值。
#### *Number( )*
使用`Number`函数,可以将任意类型的值转化成数值。
下面分成两种情况讨论,一种是参数是原始类型的值,另一种是参数是对象。
**1原始类型值**
原始类型值的转换规则如下。
```js
// 数值:转换后还是原来的值
Number(324) // 324
// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324
// 字符串:如果不可以被解析为数值,返回 NaN
Number('324abc') // NaN
// 空字符串转为0
Number('') // 0
// 布尔值true 转成 1false 转成 0
Number(true) // 1
Number(false) // 0
// undefined转成 NaN
Number(undefined) // NaN
// null转成0
Number(null) // 0
```
`Number`函数将字符串转为数值,要比`parseInt`函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为`NaN`
```js
parseInt('42 cats') // 42
Number('42 cats') // NaN
```
上面代码中,`parseInt`逐个解析字符,而`Number`函数整体转换字符串的类型。
另外,`parseInt``Number`函数都会自动过滤一个字符串前导和后缀的空格。
```js
parseInt('\t\v\r12.34\n') // 12
Number('\t\v\r12.34\n') // 12.34
```
**2对象**
简单的规则是,`Number`方法的参数是对象时,将返回`NaN`,除非是包含单个数值的数组。
```js
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5
```
之所以会这样,是因为`Number`背后的转换规则比较复杂。
第一步,调用对象自身的`valueOf`方法。如果返回原始类型的值,则直接对该值使用`Number`函数,不再进行后续步骤。
第二步,如果`valueOf`方法返回的还是对象,则改为调用对象自身的`toString`方法。如果`toString`方法返回原始类型的值,则对该值使用`Number`函数,不再进行后续步骤。
第三步,如果`toString`方法返回的是对象,就报错。
请看下面的例子。
```js
var obj = {x: 1};
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`
默认情况下,对象的`valueOf`方法返回对象本身,所以一般总是会调用`toString`方法,而`toString`方法返回对象的类型字符串(比如`[object Object]`)。所以,会有下面的结果。
```js
Number({}) // NaN
```
如果`toString`方法返回的不是原始类型的值,结果就会报错。
```js
var obj = {
valueOf: function () {
return {};
},
toString: function () {
return {};
}
};
Number(obj)
// TypeError: Cannot convert object to primitive value
```
上面代码的`valueOf``toString`方法,返回的都是对象,所以转成数值时会报错。
从上例还可以看到,`valueOf``toString`方法,都是可以自定义的。
```js
Number({
valueOf: function () {
return 2;
}
})
// 2
Number({
toString: function () {
return 3;
}
})
// 3
Number({
valueOf: function () {
return 2;
},
toString: function () {
return 3;
}
})
// 2
```
上面代码对三个对象使用`Number`函数。第一个对象返回`valueOf`方法的值,第二个对象返回`toString`方法的值,第三个对象表示`valueOf`方法先于`toString`方法执行。
#### *String( )*
`String`函数可以将任意类型的值转化成字符串,转换规则如下。
**1原始类型值**
- **数值**:转为相应的字符串。
- **字符串**:转换后还是原来的值。
- **布尔值**`true`转为字符串`"true"``false`转为字符串`"false"`
- **undefined**:转为字符串`"undefined"`
- **null**:转为字符串`"null"`
```js
String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"
```
**2对象**
`String`方法的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。
```js
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
```
`String`方法背后的转换规则,与`Number`方法基本相同,只是互换了`valueOf`方法和`toString`方法的执行顺序。
1. 先调用对象自身的`toString`方法。如果返回原始类型的值,则对该值使用`String`函数,不再进行以下步骤。
2. 如果`toString`方法返回的是对象,再调用原对象的`valueOf`方法。如果`valueOf`方法返回原始类型的值,则对该值使用`String`函数,不再进行以下步骤。
3. 如果`valueOf`方法返回的是对象,就报错。
下面是一个例子。
```js
String({a: 1})
// "[object Object]"
// 等同于
String({a: 1}.toString())
// "[object Object]"
```
上面代码先调用对象的`toString`方法,发现返回的是字符串`[object Object]`,就不再调用`valueOf`方法了。
如果`toString`法和`valueOf`方法,返回的都是对象,就会报错。
```js
var obj = {
valueOf: function () {
return {};
},
toString: function () {
return {};
}
};
String(obj)
// TypeError: Cannot convert object to primitive value
```
下面是通过自定义`toString`方法,改变返回值的例子。
```js
String({
toString: function () {
return 3;
}
})
// "3"
String({
valueOf: function () {
return 2;
}
})
// "[object Object]"
String({
valueOf: function () {
return 2;
},
toString: function () {
return 3;
}
})
// "3"
```
上面代码对三个对象使用`String`函数。第一个对象返回`toString`方法的值数值3第二个对象返回的还是`toString`方法的值(`[object Object]`),第三个对象表示`toString`方法先于`valueOf`方法执行。
#### *Boolean( )*
`Boolean()`函数可以将任意类型的值转为布尔值。
它的转换规则相对简单:除了以下五个值的转换结果为`false`,其他的值全部为`true`
- `undefined`
- `null`
- `0`(包含`-0``+0`
- `NaN`
- `''`(空字符串)
```js
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
```
当然,`true``false`这两个布尔值不会发生变化。
```js
Boolean(true) // true
Boolean(false) // false
```
注意,所有对象(包括空对象)的转换结果都是`true`,甚至连`false`对应的布尔对象`new Boolean(false)`也是`true`(详见《原始类型值的包装对象》一章)。
```js
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
```
所有对象的布尔值都是`true`,这是因为 JavaScript 语言设计的时候,出于性能的考虑,如果对象需要计算才能得到布尔值,对于`obj1 && obj2`这样的场景,可能会需要较多的计算。为了保证性能,就统一规定,对象的布尔值为`true`
## 自动转换(隐式转换)
下面介绍自动转换,它是以强制转换为基础的。
遇到以下三种情况时JavaScript 会自动转换数据类型,即转换是自动完成的,用户不可见。
第一种情况,不同类型的数据互相运算。
```javascript
123 + 'abc' // "123abc"
```
第二种情况,对非布尔值类型的数据求布尔值。
```javascript
if ('abc') {
console.log('hello')
} // "hello"
```
第三种情况,对非数值类型的值使用一元运算符(即`+``-`)。
```javascript
+ {foo: 'bar'} // NaN
- [1, 2, 3] // NaN
```
自动转换的规则是这样的:预期什么类型的值,就调用该类型的转换函数。比如,某个位置预期为字符串,就调用`String()`函数进行转换。如果该位置既可以是字符串,也可能是数值,那么默认转为数值。
由于自动转换具有不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用`Boolean()``Number()``String()`函数进行显式转换。
#### 自动转换为布尔值
JavaScript 遇到预期为布尔值的地方(比如`if`语句的条件部分),就会将非布尔值的参数自动转换为布尔值。系统内部会自动调用`Boolean()`函数。
因此除了以下五个值,其他都是自动转为`true`
- `undefined`
- `null`
- `+0``-0`
- `NaN`
- `''`(空字符串)
下面这个例子中,条件部分的每个值都相当于`false`,使用否定运算符后,就变成了`true`
```javascript
if ( !undefined
&& !null
&& !0
&& !NaN
&& !''
) {
console.log('true');
} // true
```
下面两种写法,有时也用于将一个表达式转为布尔值。它们内部调用的也是`Boolean()`函数。
```javascript
// 写法一
expression ? true : false
// 写法二
!! expression
```
#### 自动转换为字符串
JavaScript 遇到预期为字符串的地方,就会将非字符串的值自动转为字符串。具体规则是,先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串。
字符串的自动转换,主要发生在字符串的加法运算时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。
```javascript
'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"
```
这种自动转换很容易出错。
```javascript
var obj = {
width: '100'
};
obj.width + 20 // "10020"
```
上面代码中,开发者可能期望返回`120`,但是由于自动转换,实际上返回了一个字符`10020`
#### 自动转换为数值
JavaScript 遇到预期为数值的地方,就会将参数值自动转换为数值。系统内部会自动调用`Number()`函数。
除了加法运算符(`+`)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。
```javascript
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 0
false / '5' // 0
'abc' - 1 // NaN
null + 1 // 1
undefined + 1 // NaN
```
上面代码中,运算符两侧的运算子,都被转成了数值。
> 注意:`null`转为数值时为`0`,而`undefined`转为数值时为`NaN`。
一元运算符也会把运算子转成数值。
```javascript
+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0
```
## 真题解答
- *JavaScript* 中如何进行数据类型的转换?
> 参考答案:
>
> 类型转换可以分为两种,**隐性转换**和**显性转换**。
>
> **1. 隐性转换**
>
> 当不同数据类型之间进行相互运算,或者当对非布尔类型的数据求布尔值的时候,会发生隐性转换。
>
> 预期为数字的时候:算术运算的时候,我们的结果和运算的数都是数字,数据会转换为数字来进行计算。
>
> | 类型 | 转换前 | 转换后 |
> | --------- | --------- | ------ |
> | number | 4 | 4 |
> | string | "1" | 1 |
> | string | "abc" | NaN |
> | string | "" | 0 |
> | boolean | true | 1 |
> | boolean | false | 0 |
> | undefined | undefined | NaN |
> | null | null | 0 |
>
> 预期为字符串的时候:如果有一个操作数为字符串时,使用`+`符号做相加运算时,会自动转换为字符串。
>
> 预期为布尔的时候:前面在介绍布尔类型时所提到的 9 个值会转为 false其余转为 true
>
> **2. 显性转换**
>
> 所谓显性转换,就是只程序员强制将一种类型转换为另外一种类型。显性转换往往会使用到一些转换方法。常见的转换方法如下:
>
> - 转换为数值类型:`Number()``parseInt()``parseFloat()`
>
> - 转换为布尔类型:`Boolean()`
>
> - 转换为字符串类型:`toString()``String()`
>
> 当然,除了使用上面的转换方法,我们也可以通过一些快捷方式来进行数据类型的显性转换,如下:
>
> - 转换字符串:直接和一个空字符串拼接,例如:`a = "" + 数据`
>
> - 转换布尔:!!数据类型,例如:`!!"Hello"`
>
> - 转换数值:数据*1 或 /1例如`"Hello * 1"`
-*EOF*-