> For the complete documentation index, see [llms.txt](https://lizh.gitbook.io/knowledge/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lizh.gitbook.io/knowledge/javascript-xi-lie/01-ji-chu-pian-02-lei-xing-zhuan-huan.md).

# 基础篇 02 类型转换

类型转换（Type Conversion）是指将数据由一种类型变换为另一种类型。在 JavaScript 中，在一些运算类型与预期不符的情况下，会发生隐式转换，也可以用一些写法强制要求进行显式转换。

JavaScript 中有 7 种简单数据类型（也称为基本数据类型）：数字（Number）、字符串（String）、布尔值（Boolean）、 Undefined、Null，还有 ES6 新增的 Symbole，以及处于第 4 阶段的 BigInt。此外，还有 1 种复杂数据类型（也称引用类型）—对象（Object）。

## 强制类型转换

强制类型转换主要是使用 Number()、String()、Boolean() 三个函数，手动将各种类型的值，分别转换成数字、字符串、布尔值。

### Number()

Number 对象是经过封装的处理数值的对象，主要有两个作用：一是作为构造函数，创建数字对象；二是用来将任意类型的值转换为数值。

#### 原始类型值

* 数值：返回原值。如果是极小和极大的数字，则会返回指数形式。
* 字符串：可以被解析，则转换为相应的数值；不可以被解析为数值，返回 NaN；空字符串转为 0。
* 布尔值：true 转成 1；false 转成 0。
* undefined：转成为 NaN。
* null：转成为 0。
* Symbol：不能转换为数字，会报错。
* BigInt：转换为数值，末尾 n 会丢失。如果是极小和极大的数字，会丢失精度。

```javascript
Number(324) // 324

Number('324')    // 324
Number('324abc') // NaN
Number('')       // 0

Number(true)  // 1
Number(false) // 0

Number(undefined) // NaN
Number(null)      // 0

Number(Symbol('a')) // Uncaught TypeError: Cannot convert a Symbol value to a number
```

Number() 函数将字符串转为数值，要比 parseInt() 函数严格很多。基本上，只要有一个字符无法转成数值，整个字符串就会被转为 NaN。

另外，parseInt() 和 Number() 函数都会自动过滤一个字符串前导和后缀的空格。

#### 对象

简单的规则是，Number() 函数的参数是对象时，将返回 NaN，除非是空数组或者只有一个数值的数组。

```javascript
Number({})         // NaN
Number({a: 1})     // NaN
Number([1, 2, 3])  // NaN
Number([])         // 0
Number([5])        // 5
Number(new Date()) // 1653321770387
```

具体规则：

* 调用对象自身的 valueOf() 方法。如果返回原始类型的值，则使用 Number() 函数转换该值。
* 如果 valueOf() 方法返回的还是对象，则改为调用对象自身的 toString() 方法；如果 toString() 方法返回原始类型的值，则使用 Number() 函数转换该值。
* 如果 toString() 方法返回的是对象，则报错。

```javascript
const obj = {}
obj.valueOf()     // {}
obj.toString()    // '[object Object]'
Number('[object Object]') // NaN
```

另外，一个对象的 valueOf() 和 toString() 方法，是可以自定义的。

```javascript
const obj = {
    valueOf: function () { return {}; },
    toString: function () { return {}; }
};
obj.valueOf()  // {}
obj.toString() // {}
Number(obj)    // Uncaught TypeError: Cannot convert object to primitive value
```

### String()

String 是一个用于字符串或一个字符序列的构造函数，另外，还可以作普通函数使用，可以将任意类型的值转化成字符串。

#### 原始类型值

* 数值：转换为相应的字符串。
* 字符串：返回原值。
* 布尔值：true 转换为字符串 "true"；false 转换为字符串 "false"。
* undefined：转换为字符串 "undefined"。
* null：转换为字符串 "null"。
* Symbol：可以显式强制转换为字符串，然而隐式强制类型转换会产生错误。
* BigInt：转换为数值，末尾 n 会丢失。

```javascript
String(123)         // '123'
String('abc')       // 'abc'
String(true)        // 'true'
String(undefined)   // 'undefined'
String(null)        // 'null'
String(Symbol('a')) // 'Symbol(a)'
'a' + Symbol('a')   // Uncaught TypeError: Cannot convert a Symbol value to a string
String(BigInt(1))   // '1'
```

#### 对象

String 函数的参数如果是对象，返回一个类型字符串；如果是数组，返回该数组的字符串形式。

```javascript
String({a: 1})     // '[object Object]'
String({})         // '[object Object]'
String([1, 2, 3])  // '1,2,3'
String([])         // ''
String(new Date()) // 'Tue May 24 2022 00:03:00 GMT+0800 (中国标准时间)'
```

具体转换规则：

* 先调用对象自身的 toString() 方法。如果返回原始类型的值，则使用 String() 函数转换该值。
* 如果 toString() 方法返回的是对象，再调用原对象的 valueOf() 方法。如果 valueOf() 方法返回原始类型的值，则使用 String() 函数转换该值。
* 如果 valueOf() 方法返回的是对象，则报错。

### Boolean()

Boolean 对象是一个布尔值的对象包装器，也可以用作普通函数，将任意类型的值转为布尔值。

如果是布尔值，则原值返回，仅以下五个值的转换为 false，其他的值全部为 true：

* undefined
* null
* 0（包含 -0、+0、BigInt(0)）
* NaN
* ''

```javascript
Boolean(undefined) // false
Boolean(null)      // false
Boolean(0)         // false
Boolean(NaN)       // false
Boolean('')        // false
```

**注意：** 所有对象（包括空对象、Symbol 值）的转换都是 true，甚至连 false 对应的布尔对象 `new Boolean(false)` 也是。

```javascript
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
Boolean(Symbol('a'))        // true
```

**注意：** 有一个特殊的对象，它转换成的布尔值是 false。

```javascript
document.all // HTMLAllCollection(9) [html, head, meta, meta, title, body, div, script, script, viewport: meta]
Boolean(document.all) // false
```

## 隐式类型转换

隐式类型转换是以强制类型转换为基础的。

以下三种情况时，JavaScript 会自动转换数据类型：

* 不同类型的数据互相运算。
* 对非布尔值类型的数据求布尔值。
* 对非数值类型的值使用一元运算符（即 +、-）。

自动转换的规则：预期什么类型的值，就调用该类型的转换函数。如果该位置既可以是字符串，也可能是数值，那么默认转为数值。

比如，`'' + a`，调用 String(a) 转换字符串；`+ a`，调用 Number(a) 转换为数值。

由于自动转换具有不确定性，而且不易除错，建议在预期为布尔值、数值、字符串的地方，全部使用 Boolean()、Number() 和 String() 函数进行强制转换。

### 自动转换为布尔值

以下情况，JavaScript 会自动调用 Boolean() 函数：

* if 语句的条件部分
* for (;;) 语句中的条件判断表达式（第二个）
* while () 和 do...while() 循环中的条件判断表达式
* 三元表达式（?:）
* 逻辑否运算符（&&、||、!）

### 自动转换为字符串

以下情况，JavaScript 会自动调用 String() 函数：

字符串的加号运算符（+）：当两个操作数，有一个是字符串，另一个为非字符串，则后者转为字符串。

```javascript
'1' + 1               // '11'
'1' + true            // '1true'
'1' + false           // '1false'
'1' + {}              // '1[object Object]'
'1' + []              // '1'
'1' + function () { } // '1function () { }'
'1' + undefined       // '1undefined'
'1' + null            // '1null'
```

### 自动转换为数值

以下情况，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
```

## 对象转基本类型

对象转基本类型有三种变体，发生在各种情况下，这些情况被称为 hint（暗示、示意）。ECMAScript 规范定义：当一个对象被用在需要原始值的上下文中时，例如，在比较 alert 或数学运算中，对象会被转换为原始值。

### Hint

* string： 对象到字符串的转换。当对期望一个字符串的对象执行操作，如，alert 函数中的参数。
* number：对象到数字的转换。如，进行数学运算时。
* default：在少数情况下发生，当运算符 “不确定” 期望值的类型时，如二元加法 +、不严格相等 ==、比较运算符 <、>。

为了实现转换，JavaScript 会遵循以下规则顺序：

* 如果 Symbol.toPrimitive 方法存在，则它会被用于所有 hint，无需更多其他方法。
* 否则，如果 hint 是 `"string"` —— 顺序尝试对象的 toString() 和 valueOf()，无论哪个存在。
* 否则，如果 hint 是 `"number"` 或 `"default"` —— 顺序尝试对象的 valueOf() 和 toString()，无论哪个存在。

### Symbol.toPrimitive

Symbol.toPrimitive 是一个内置的 Symbol 值，它是作为对象的函数值属性存在的，当一个对象转换为对应的原始值时，会调用此函数。

```javascript
const user = {
    name: "John",
    money: 1000,

    [Symbol.toPrimitive](hint) {
        console.log(`hint: ${hint}`);
        return hint == "string" ? `{name: "${this.name}"}` : this.money;
    }
};
alert(user); // hint: string
(+user);      // hint: number
(user + 500); // hint: default
```

唯一强制性的事情是：这些方法必须返回一个原始值，而不是对象。

### toString()和valueof()

toString() 方法返回一个表示该对象的字符串，valueOf() 方法返回指定对象的原始值。

如果没有 Symbol.toPrimitive 方法，那么 JavaScript 将尝试寻找 toString() 和 valueOf() 方法：

* 对于 “string” hint：如果 toString() 存在，优先调用 toString()；否则调用 valueOf()。
* 对于其他 hint：如果 valueOf() 存在，优先调用 valueOf()；否则调用则 toStrin()。

这些方法必须返回一个原始值。如果 toString() 或 valueOf() 返回了一个对象，那么返回值会被忽略（和没有方法时相同）。

默认情况下，普通对象具有 toString() 和 valueOf() 方法：

* toString() 方法返回一个字符串 "\[object Object]"。
* valueOf() 方法返回对象自身。

一般地，toString() 作为 “全能” 的方法来处理转换。

## ES6 的四种相等性判断

### 相等（==）

相等运算符（==）检查其两个操作数是否相等，并返回布尔值。

相等运算符（==）会尝试强制类型转换并且比较不同类型的操作数，转换规则如下：

* 如果两个操作数都是对象，则仅当两个操作数都引用同一个对象时才返回 true。
* 如果一个操作数是 null，另一个操作数是 undefined，则返回 true。
* 一个操作数是 undefined 或者 null，如果另一个操作数是 Number、String、Boolean ，则返回 false；如果另一个操作数是对象，则返回 `!Boolean(object)`（特殊对象 document.all，查看下方示例）。
* 如果两个操作数是不同类型的，就会尝试在比较之前将它们转换为相同类型：
  * 当数字与字符串进行比较时，会尝试将字符串转换为数字值。
  * 如果操作数之一是 Boolean，则 true 转换为1、false 转换为 0。
  * 如果操作数之一是对象，另一个是数字或字符串，会尝试使用对象的 valueOf() 和 toString() 方法将对象转换为原始值。
* 如果操作数具有相同的类型，则将它们进行如下比较：
  * String： 仅当两个操作数具有相同顺序的相同字符时才返回 true。
  * Number：仅当两个操作数具有相同的值时才返回 true。+0 和 -0 视为相同的值，NaN 和任何值（包括 NaN 本身）返回 false。
  * Boolean：仅当操作数为两个 true 或两个 false 时才返回 true。

以下表格为不同类型一一对应的转换规则：

**备注：** 表格纵向为被比较值 A，横向为被比较值 B；ToNum(A) 表示将 A 转换为数字；ToPrim(A) 表示调用 A.toString() 和 A.valueOf() 方法，将 A 转换为原始值；IsFalsy(A) 表示 A 是否为假值。

|           | undefined  | null       | Number         | String                | Boolean               | Object                |
| --------- | ---------- | ---------- | -------------- | --------------------- | --------------------- | --------------------- |
| undefined | true       | true       | false          | false                 | false                 | IsFalsy(B)            |
| null      | true       | true       | false          | false                 | false                 | IsFalsy(B)            |
| Number    | false      | false      | A===B          | A === ToNum(B)        | A === ToNum(B)        | A== ToPrim(B)         |
| String    | false      | false      | ToNum(A) === B | A===B                 | ToNum(A) === ToNum(B) | ToPrim(B) == A        |
| Boolean   | false      | false      | ToNum(A) === B | ToNum(A) === ToNum(B) | A===B                 | ToNum(A) == ToPrim(B) |
| Object    | IsFalsy(A) | IsFalsy(A) | ToPrim(A) == B | ToPrim(A) == B        | ToPrim(A) == ToNumB   | A===B                 |

一些特殊的比较：

```javascript
document.all
// HTMLAllCollection(10) [html, head, meta, meta, title, style, body, div, a, script, viewport: meta]
console.log(document.all == undefined) // true
console.log(document.all == null)      // true
```

### 全等运算符（===）

全等运算符（===）会检查它的两个操作数是否相等，并且返回一个布尔值结果。与相等运算符不同，全等运算符总是认为不同类型的操作数是不同的，也就是说，不会隐藏转换类型。

全等运算符（===）比较规则：

* 如果操作数的类型不同，则返回 false。
* 如果两个操作数都是对象，只有当它们指向同一个对象时才返回 true。
* 如果两个操作数都为 null，或者两个操作数都为 undefined，返回 true。
* 如果两个操作数有任意一个为 NaN，返回 false。
* 否则，比较两个操作数的值：
  * 数字类型必须拥有相同的数值。+0 和 -0 会被认为是相同的值。
  * 字符串类型必须拥有相同顺序的相同字符。
  * 布尔运算符必须同时为 true 或同时为 false。

### 同值相等

确定两个值是否在任何情况下功能上是相同的，可以通过 Object.is() 方法来判断。

与严格相等（===）只有两处不同：

* +0 不等于 -0。
* NaN 等于自身。

```javascript
Object.is(+0, -0)   // false
Object.is(NaN, NaN) // true
```

### 零值相等

与同值相等类似，不过会认为 +0 与 -0 相等。

```javascript
+0 === -0 // true
```

零值相等使用场景：%TypedArray% 和 ArrayBuffer 构造函数、以及 Map、Set 操作, 并将用于 ES2016/ES7 中的 String.prototype.includes 方法。

```javascript
const map = new Map();
const obj = { a: 'a' }
map.set(+0, 1)
map.get(+0) // 1
map.get(-0) // 1
```

## 一些例子

```javascript
![]     // false
+[]     // 0
+![]    // 0
[] + [] // '
{} + {} // '[object Object][object Object]'
{} + [] // 0
{a:0} + 1 // 1
[] + {}   // '[object Object]'
[] + ![]  // 'false'
{} + []   // 0
![] + []  // 'false'
'' + {}   // '[object Object]'
{} + ''   // 0
[]['map'] + [] // 'function map() { }'
[]['a'] + []   // 'undefined'
[][[]] + []    // 'undefined'
+!![] + []     // '1'
+!![]    // 1
1 - {}   // NaN
1 - []   // 1
true - 1 // 0
{} - 1   // -1
[]==![]  // true

+((+![])+(+!![])+[]+(+![])) // 10
(![]+[])[+[]]   // 'f'。'false'[0]
(![]+[])[+!![]] // 'a'。'false'[1]

```

## 相关问题

### (a==1 && a==2 && a==3) 能不能为 true？

事实上是可以的。

因为在 `==` 比较的情况下，会进行类型的隐式转换。如果参数是对象，先调用 valueOf() 方法，再调用 toString() 方法。因此，只要改变原生的 valueOf() 方法或者 toString() 方法就可以达到效果。

```javascript
const a = {
    num: 0,
    valueOf: function () {
        return this.num += 1
    }
};
console.log(a == 1 && a == 2 && a == 3) // true
```

```javascript
let num = 0;
Function.prototype.toString = function () {
    return ++num;
}
function a() { }
console.log(a == 1 && a == 2 && a == 3) // true
```

```javascript
const a = {
    [Symbol.toPrimitive]: (function (i) {
        return function () { return ++i }
    })(0)
};
console.log(a == 1 && a == 2 && a == 3) // true
```

### 完善Cash打印三个101

要求只能在 class 里面增加代码：

```javascript
class Cash {}
const a = new Cash(1)
const b = new Cash(100)
console.log(`${a.add(b)},${Cash.add(a,b)},${new Cash(a+b)}`) // '101,101,101'
```

首先，三个输出结果是以隐式转换的形式出现的，这是关键之处。 a 和 b 都是 new 出来的对象，由 `new Cash(a+b)` 可以看出构造函数传入的也是两个 `Cash` 的实例对象。那么 new 出来的结果肯定不是简简单单的一个对象，不然就是被转换成字符串 '\[object Object]'，但是又不得不以对象类型出现，那就只能魔改隐式转换用到的toString() 和 valueOf() 方法。

```javascript
class Cash {
    constructor(a) {
        this.m = a
        this.valueOf = function () { return a }
    }
    add(n) { return this.m + n }
    toString() { return this.m }
    static add(v1, v2) { return v1 + v2 }
}
const a = new Cash(1)
const b = new Cash(100)
console.log(`${a.add(b)},${Cash.add(a, b)},${new Cash(a + b)}`) // '101,101,101'
```

## 参考资料

[阮一峰 - 数据类型的转换](https://wangdoc.com/javascript/features/conversion.html)

[玩转js类型转换](https://imweb.io/topic/5be2f05a21ff0e9610a6646e)

[对象 — 原始值转换](https://zh.javascript.info/object-toprimitive)

[MDN - JavaScript 中的相等性判断](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Equality_comparisons_and_sameness)

[MDN - 相等（==）](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Equality)
