> 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-01-shu-ju-lei-xing-zhi-ji-ben-lei-xing.md).

# 基础篇 01 数据类型之基本类型

编程语言都具有内建的数据结构，但各种编程语言的数据结构常有不同之处。

JavaScript 是一种**弱类型**或者说**动态语言**。这意味着不用提前声明变量的类型，在程序运行过程中，类型会被自动确定。

这也意味着你可以使用同一个变量保存不同类型的数据：

```javascript
let foo = 42;
foo     = 'bar';
foo     = true;
```

## 基本类型和引用类型

JavaScript 语言中类型集合由原始值和对象组成，一般称原始值为基本类型，称对象为引用类型。

### 基本类型（7种）

JavaScript 数据的基本类型原本有五种：字符串（String）、数值（Number）、布尔值（Boolean）、undefined、null，ES6 引入了一种新的原始数据类型 Symbol，表示独一无二的值，另外还有一种表示任意大的整数的 BigInt 类型（目前是第 4 阶段提案）。

**注意：** 由原始数据类型创建一个显式包装器对象从 ES6 开始不再被支持。 然而，现有的原始包装器对象，如 new Boolean、new String、new Number，因为遗留原因仍可被创建。

基本数据类型的值是**按值**访问的，有以下特点：

* 基本类型的值是不可变的。**注意：** 基本类型变量的修改，是被赋予一个新值，而不是改变原值，也就是说，新值是用新内存创建的，而不是在原值内存中修改的。
* 基本类型的比较是**值的比较**。
* 基本类型的变量是存放在栈内存里的。

### 引用类型

JavaScript 中的对象是引用类型，包括：Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型等，以及 ES6 新增的 Map 类型、WeakMap 类型、Set 类型、WeakSet 类型等。

引用类型的值是**按引用**访问的，有以下特点：

* 引用类型的值是可变的。
* 引用类型的比较是引用的比较。
* 引用类型的变量是存放在栈内存中的一个指针，其值是保存在堆内存中的对象，其指针指向堆内存中的对象。

### 一个简单示例

```javascript
let a = 1
const b = a
a = 2
console.log(a) // 2
console.log(b) // 1
```

```javascript
let a = {
    num: 1
}
const b = a
a.num = 2
console.log(a.num) // 2
console.log(b.num) // 2
```

## 字符串类型（String）

字符串是 JavaScript 的一种基本数据类型，用于表示文本数据。

字符串是一组 16 位的无符号整数值的“元素”。在字符串中的每个元素占据了字符串的位置。第一个元素的索引为 0，下一个是索引 1，依此类推（类数组）。

### 转义字符

除了普通的可打印字符以外，一些有特殊功能的字符可以通过转义字符的形式放入字符串中。

Javascript 字符串的转义符是反斜杠（`\`）：

* `\0`：空字符
* `\'`：单引号
* `\"`：双引号
* `\\`：反斜杠
* `\n`：换行
* `\r`：回车
* `\v`：垂直制表符
* `\t`：水平制表符
* `\b`：退格
* `\f`：换页
* `\uXXXX`：\u + 四个十六进制数（0000\~FFFF），代表一个字符。XXXX 对应该字符的 Unicode 码点，比如\u00A9 表示版权符号（`©`）。
* `\xHH`：\x + 两个十六进制数（00\~FF），代表一个字符。HH 对应该字符的 Unicode 码点，比如 \xA9 表示版权符号（`©`），这种方法也只能输出 256 种字符。
* `\HHH`：三个八进制数（000\~377），代表一个字符。HHH 对应该字符的 Unicode 码点，比如 \251 表示版权符号（`©`），这种方法只能输出 256 种字符。

**注意：** 和其他语言不同，Javascript 的字符串不区分单引号（`‘’`）和双引号（`“”`），所以不论是单引号还是双引号的字符串，上面的转义字符都能运行 。

### 模板字符串（template string）

ES6 引入了模板字符串。模板字符串是增强版的字符串，用反引号（\`）标识。它可以当作普通字符串使用，也可以用来定义多行字符串，或者在字符串中嵌入变量。

```javascript
const who = 'lizh'
`hello world!`
`hello ${who}!`
```

### 字符集

JavaScript 引擎内部，所有字符都使用 Unicode 字符集，且允许在程序中使用 Unicode 码点表示字符：

```javascript
'\u00A9' // '©'
const f\u006F\u006F = 'abc'; // 即 const foo = 'abc'
```

每个字符在 JavaScript 内部都是以 16 位（即 2 个字节）的 UTF-16 格式储存。也就是说，JavaScript 的单位字符长度固定为 16 位长度，即 2 个字节。

但是，UTF-16 有两种长度：对于码点在 U+0000~~U+FFFF 之间的字符，长度为 16 位（即 2 个字节）；对于码点在 U+10000~~U+10FFFF 之间的字符，长度为 32 位（即 4 个字节），而且前两个字节在 0xD800~~0xDBFF 之间，后两个字节在 0xDC00~~0xDFFF 之间。

JavaScript 对 UTF-16 的支持是不完整的，由于历史原因，只支持两字节的字符，不支持四字节的字符。这是因为 JavaScript 第一版发布的时候，Unicode 的码点只编到 U+FFFF，因此两字节足够表示了。后来，Unicode 纳入的字符越来越多，出现了四字节的编码。但是，JavaScript 的标准此时已经定型了，统一将字符长度限制在两字节，导致无法识别四字节的字符。

> ES6 对这一点做出了改进，只要将码点放入大括号，就能正确解读该字符。
>
> ```javascript
> "\u{20BB7}"
> ```

对于码点在 U+10000\~U+10FFFF 之间的字符，JavaScript 总是认为它们是两个字符（即，length 属性为 2），因此，JavaScript 返回的字符串长度可能是不正确的。

### Base64 转码

有时，文本里面包含一些不可打印的符号，比如 ASCII 码 0\~31 的符号都无法打印出来，这时可以使用 Base64 编码，将它们转成可以打印的字符。另一个场景是，有时需要以文本格式传递二进制数据，那么也可以使用 Base64 编码。

所谓 Base64 就是一种编码方法，可以将任意值转成 0～9、A～Z、a-z、`+` 和 `/` 这64个字符组成的可打印字符。使用它的主要目的，不是为了加密，而是为了不出现特殊字符，简化程序的处理。

JavaScript 原生提供两个 Base64 相关的方法：

* `btoa()`：任意值转为 Base64 编码；
* `atob()`：Base64 编码转为原来的值；

```javascript
btoa('Hellow, world!')       // 'SGVsbG93LCB3b3JsZCE='
atob('SGVsbG93LCB3b3JsZCE=') // 'Hellow, world!'
```

需要注意的是，这两个方法不适合非 ASCII 码的字符，会报错：

```javascript
btoa('你好') // VM979:1 Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.
```

解决方法是先用 encodeURIComponent 编码：

```javascript
btoa(encodeURIComponent('你好')) // 'JUU0JUJEJUEwJUU1JUE1JUJE'
decodeURIComponent(atob('JUU0JUJEJUEwJUU1JUE1JUJE')) // '你好'
```

### String对象

String 是一个表示字符串的 Javascript 标准内置对象，也是创建字符串对象的构造函数。

```javascript
let s1 = new String('abc'); // String {'abc'}
let s2 = String('abc');     // 'abc'
```

### 字符串对象和基本字符串值

字符串字面量（通过单引号或双引号定义）和调用 String 方法（没有通过 new 生成字符串对象实例）的字符串都是基本字符串。当基本字符串需要调用一个字符串对象才有的方法或者查询值的时候，JavaScript 会自动将基本字符串转化为字符串对象并且调用相应的方法或者执行查询。

```javascript
const s1 = "abc";
const s2 = new String(s1)

console.log(typeof s1); // 'string'
console.log(typeof s2); // 'object'
```

当使用 eval 时，基本字符串和字符串对象也会产生不同的结果。eval 会将基本字符串作为源代码处理；而字符串对象则被看作对象处理，返回对象。

```javascript
const s1 = "2 + 2";
const s2 = new String("2 + 2");

console.log(eval(s1)); // 4
console.log(eval(s2)); // String {'2 + 2'}
```

### String静态方法

#### fromCharCode()

通过一串 Unicode 创建字符串，参数是一系列 UTF-16 代码单元的数字。范围介于 0\~65535（0xFFFF）之间。大于 0xFFFF 的数字将被截断，不进行有效性检查。

```javascript
String.fromCharCode(100) // 'd'
String.fromCharCode(189, 43, 190, 61) // '½+¾='
```

**注意：** 码点大于 0xFFFF 的字符占用四个字节，而 JavaScript 默认支持两个字节的字符。

**解决方法：** 将大于 0xFFFF 的字符拆成两个字符表示。

```javascript
String.fromCharCode(0x20BB7)        // 'ஷ'
String.fromCharCode(0xD842, 0xDFB7) // '𠮷'
```

#### fromCodePoint()

通过一串 Unicode 编码位置创建字符串。**ES6 新增加的方法。**

**注意：** 如果传入无效的 Unicode 编码，将会抛出一个RangeError。

```javascript
String.fromCodePoint(100) // 'd'
String.fromCodePoint(10000000) // Uncaught RangeError: Invalid code point 10000000
String.fromCodePoint(NaN) // Uncaught RangeError: Invalid code point NaN
```

#### raw()

通过模板字符串创建字符串。**ES6 新增加的方法。**

```javascript
String.raw `Hi\n${2+3}!`;  // 'Hi\\n5!'
String.raw `Hi\u000A!`;    // 'Hi\\u000A!'

const name = "Bob";
String.raw `Hi\n${name}!`; // 'Hi\\nBob!'

String.raw({ raw: 'test' }, 0, 1, 2); // 't0e1s2t'
String.raw( // 'foo5barJavaScriptbaz'
    {
        raw: ['foo', 'bar', 'baz']
    },
    2 + 3,
    'Java' + 'Script'
)
```

### String属性

#### length

属性表示一个字符串的长度。

该属性返回字符串中字符**编码单元的数量**。JavaScript 使用 UTF-16 编码，该编码使用一个 16 位的编码单元来表示大部分常见的字符，使用**两个代码单元**表示不常用的字符。因此 length 返回值可能与字符串中实际的字符数量不相同。

### String实例方法

#### charAt()

返回一个字符串中返回指定的字符。参数是一个介于0 和字符串长度减 1 之间的整数，不传或者无效参数，则默认为 0，如果超出范围，则返回一个空字符串。

**注意：** 该方法完全可以用数组下标替代。

#### charCodeAt()

返回字符串指定位置字符的 Unicode 码点（0\~65535 之间的整数），相当于 `String.fromCharCode()` 的逆操作。参数是一个介于0 和字符串长度减 1 之间的整数，不传或者无效参数，则默认为 0，如果超出范围，返回 NaN。

#### codePointAt()（ES6）

返回字符串中给定索引的字符的 Unicode 编码点值。参参数是一个介于0 和字符串长度减 1 之间的整数，不传或者无效参数，则默认为 0，如果超出范围，则返回 undefined。

#### concat()

将一个或多个字符串与原字符串连接合并，形成一个新的字符串并返回。如果参数不是字符串类型，它们在连接之前将会被转换成字符串。**可使用赋值操作符（+、+=）代替。**

#### endsWith()（ES6）

判断当前字符串是否是以给定的子字符串结尾，返回布尔值。

```javascript
str.endsWith(searchString[, length])
```

length 是指定字符串的长度，默认值为 str.length。

```javascript
const str = "To be, or not to be, that is the question.";

str.endsWith("question.") // true
str.endsWith("to be")     // false
str.endsWith("to be", 19) // true
```

#### startsWith()（ES6）

判断当前字符串是否以给定的子字符串开头，返回布尔值。其他与 endsWith() 类似。

#### includes()（ES6）

方法用于判断一个字符串是否包含在另一个字符串中，返回布尔值。

```javascript
str.includes(searchString[, position])
```

position 表示从当前字符串的哪个索引位置开始搜寻子字符串，默认值为 0。

#### indexOf()

返回指定字符串在当前字符串第一次出现的索引，如果未找到，则返回 -1。

```javascript
str.indexOf(searchValue [, fromIndex])
```

searchValue 是被查找的字符串值。如果没有提供确切地提供字符串，则默认为字符串 "undefined"。

fromIndex 表示开始查找的位置，可以是任意整数，默认值为 0。如果小于 0 或者大于 str.length ，则分别从 0 或 str.length 开始。

#### lastIndexOf()

与 indexOf() 类似，区别是在字符串中从后向前搜索。

#### localeCompare()

返回一个数字，表示引用字符串在排序中与比较字符串位置。

* -1：引用字符串在比较字符串**前**面；
* 1：引用字符串在比较字符串**后**面；
* 0：两者位置相同。

```javascript
str.localeCompare(compareString[, locales[, options]])
```

locales 表示一种或多种语言或区域的一个符合 BCP 47 标准的字符串或一个字符串数组。

options 包含一系列选项：localeMatcher、usage、sensitivity、ignorePunctuation、numeric、caseFirst。

locales 和 options 参数完全取决于实现，在旧的实现中忽略这两个参数。

```javascript
'a'.localeCompare('c')           // -1
'check'.localeCompare('against') // 1
'a'.localeCompare('a')           // 0
```

#### match()

返回一个字符串匹配正则表达式的数组，数组成员为匹配的第一个字符串，没有找到匹配，则返回 null。参数是一个正则表达式对象，如果传入一个非正则表达式对象，则会隐式地使用 `new RegExp(obj)` 将其转换为正则表达式 ，如果没有参数，得到一 个包含空字符串的数组。

如果正则表达式不包含 g 标志，返回与 `RegExp.exec()` 相同的结果。

```javascript
str.match(regexp)
```

```javascript
'abc'.match('b')
// ['b', index: 1, input: 'abc', groups: undefined]
```

#### matchAll()

返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

**注意：** 正则表达式必须是设置了全局模式 g 的形式，否则会抛出异常 TypeError。

```javascript
const matches = 'abccd'.matchAll('c')
for (const match of matches) {
    console.log(match);
}
// ['c', index: 2, input: 'abccd', groups: undefined]
// ['c', index: 3, input: 'abccd', groups: undefined]
```

#### normalize()（ES6）

按照指定的一种 Unicode 正规形式将当前字符串正规化，如果该值不是字符串，则首先将其转换为一个字符串。

```javascript
str.normalize([form])
```

form 有四种 Unicode 正规形式：NFC|NFD|NFKC|NFKD，默认值为 NFC。

```javascript
const str = "\u1E9B\u0323";
str.normalize("NFC");  // ẛ̣
str.normalize();       // ẛ̣
str.normalize("NFD");  // ẛ̣
str.normalize("NFKC"); // ṩ
str.normalize("NFKD"); // ṩ
```

#### padEnd()（ES6）

用一个字符串填充当前字符串，返回填充后达到指定长度的字符串。从当前字符串的\*\*末尾（右侧）\*\*开始填充。

```javascript
str.padEnd(targetLength [, padString])
```

targetLength 是当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度，则返回当前字符串本身。

padString 是填充字符串。如果字符串太长，使填充后的字符串长度超过了目标长度，则只保留最左侧的部分，其他部分会被截断。此参数的缺省值为空格（" "）。

```javascript
'abc'.padEnd(10);        // 'abc       '
'abc'.padEnd(10, "foo"); // 'abcfoofoof'
'abc'.padEnd(1)          // 'abc'
```

#### padStart()（ES6）

与 padEnd() 类似，区别是从当前字符串的\*\*开头（左侧）\*\*开始填充。

#### repeat()（ES6）

返回指定重复次数的复制字符串。

```javascript
str.repeat(count)
```

count 是介于 0 和 +Infinity 之间的整数，表示重复多少遍原字符串。

```javascript
"abc".repeat(-1)  // Uncaught RangeError: Invalid count value
"abc".repeat(0)   // ''
"abc".repeat(2)   // 'abcabc'
"abc".repeat(3.5) // 'abcabcabc'. 自动转换成整数
```

#### replace()

返回一个由替换值替换匹配项后的新字符串，匹配项可以是一个字符串或者一个正则表达式，替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。

```javascript
str.replace(regexp|substr, newSubStr|function)
```

```javascript
'Twas the night before Xmas...'.replace(/xmas/i, 'Christmas')
// 'Twas the night before Christmas...'

"Apples are round, and apples are juicy.".replace(/apples/gi, 'oranges')
// 'oranges are round, and oranges are juicy.'

"John Smith".replace(/(\w+)\s(\w+)/, "$2, $1")
// 'Smith, John'

'className'.replace(/[A-Z]/g, '-' + '$&'.toLowerCase());
// 'class-Name'
```

替换字符串可以插入特殊变量名：

* `$$`：插入一个 "$"
* `$&`：插入匹配的子串
* \`$\`\`：插入当前匹配的子串左边的内容
* `$'`：插入当前匹配的子串右边的内容
* `$n`：正则表达式中第 n 个括号匹配的字符串，索引是从 1 开始
* `${name}`： name 是一个分组名称，只有在支持命名分组捕获的浏览器中才能使用

**注意：** 如果 replace 的第二个参数是一个函数，该函数的返回值将替换掉第一个参数匹配到的结果。该函数的传参如下：

```javascript
"123abcd".replace(/(a)(\w)c(?<name>d)/g, function (match, p1, p2, p3, offset, string, NamedCaptureGroup) {
    console.log(match)
    console.log(p1)
    console.log(p2)
    console.log(p3)
    console.log(offset)
    console.log(string)
    console.log(NamedCaptureGroup)
});
// abcd
// a
// b
// d
// 3
// 123abcd
// {name: 'd'}
```

参数说明：

* match：匹配的子串。
* p1,p2, ...：代表第 n 个括号匹配的字符串。
* offset： 匹配到的子字符串在原字符串中的偏移量。
* string：被匹配的原字符串。
* NamedCaptureGroup：命名捕获组匹配的对象。

#### replaceAll()（ES6）

返回一个所有满足匹配项的部分都已被替换值替换的新字符串。与 replace() 类似，区别是它会替换字符串中所有满足匹配的字符。

**注意：** 当使用一个正则表达式时，必须设置全局标志 `g`。

#### search()

返回一个正则表达式的搜索匹配结果，如果匹配成功，返回正则表达式在字符串中首次匹配项的索引，否则，返回 -1。如果传参是一个非正则表达式，则会使用 `new RegExp(regexp)` 隐式地将其转换为正则表达式对象。

slice()： 提取字符串的一部分，并返回一个新的字符串，且不会改动原字符串。

```javascript
str.slice(beginIndex[, endIndex])
```

beginIndex 表示从该索引（以 0 为基数）处开始提取原字符串中的字符。如果值为负数，会被当做 **字符串长度 + beginIndex**。

endIndex 表示在该索引（以 0 为基数）处结束提取字符串。如果省略该参数，则会提取到字符串末尾。如果值为负数，会被当做 **字符串长度 + endIndex**。

**注意：** 该方法提取的新字符串包括 beginIndex 但不包括 endIndex。

**注意：** 如果 beginIndex 大于 endIndex，返回一个空字符串。

```javascript
'abcdef'.slice(1 ,2)   // 'b'
'abcdef'.slice(2, 1)   // ''
'abcdef'.slice(-2)     // 'ef',相当于 'abcdef'.slice('abcdef'.length - 2)
'abcdef'.slice(-3, -1) // 'de'
```

#### split()

使用指定的分隔符将字符串分割成子字符串数组。

```javascript
str.split([separator[, limit]])
```

```javascript
"Hello World!".split() // ['Hello World!']
"Hello".split('')     // ['H', 'e', 'l', 'l', 'o']
"How are you doing?".split(' ')    // ['How', 'are', 'you', 'doing?']
"How are you doing?".split(' ', 2) // ['How', 'are']
```

可以用正则表达示来做分割符，如果正则表达式中包含捕获括号，则返回的数组包含分割符匹配部分：

```javascript
"How 1 are 2 you 3 doing?".split(/\d/)
// ['How ', ' are ', ' you ', ' doing?']

"How 1 are 2 you 3 doing?".split(/(\d)/)
// ['How ', '1', ' are ', '2', ' you ', '3', ' doing?']
```

还可以使用一个数组来作为分隔符：

```javascript
"How-are-you-doing?".split(['-'])
// ['How', 'are', 'you', 'doing?']

"How-,+are-,+you-,+doing?".split(['-', '+'])
"How-,+are-,+you-,+doing?".split(String(['-', '+']))
"How-,+are-,+you-,+doing?".split('-,+')
// ['How', 'are', 'you', 'doing?']
```

#### substr()

返回一个字符串中从指定位置开始到指定字符数的字符，首字符的索引为 0。

**注意：** substr() 并非 JavaScript 核心语言的一部分，未来将可能会被移除掉。

```javascript
str.substr(start[, length])
```

start 是开始提取字符的位置。为负值，则被看作**字符串长度 + start**；为负值且绝对值大于字符串的长度，则当作 0；为正值，且大于或等于字符串的长度，则返回一个空字符串。

length 是提取的字符数。为 0 或负值，则返回一个空字符串；不传，则提取字符直到字符串末尾。

```javascript
`abcdef`.substr(1,2)   // 'bc'
`abcdef`.substr(-2)    // 'ef'
`abcdef`.substr(-10)   // 'abcdef'
`abcdef`.substr(10, 2) // ''
`abcdef`.substr(1, 0)  // ''
```

#### substring()

返回一个字符串在开始索引到结束索引之间的一个子字符串。

```javascript
str.substring(indexStart[, indexEnd])
```

如果 indexStart 等于 indexEnd，返回一个空字符串；

如果省略 indexEnd，则提取字符一直到字符串末尾；

如果任一参数小于 0 或为 NaN，则被当作 0；

如果任一参数大于 str.length，则被当作 str.length；

如果 indexStart 大于 indexEnd，则执行效果就像两个参数调换了一样。

**注意：** substring() 提取从包括 indexStart，但不包括 indexEnd。

```javascript
`abcdef`.substring(0, 3)   // 'abc'
`abcdef`.substring(3, 0)   // 'abc'
`abcdef`.substring(3, 'a') // 'abc'
`abcdef`.substring(3, 3)   // ''
```

#### toLowerCase()

返回字符串的小写形式。

#### toUpperCase()

返回字符串的大写形式。

#### toLocaleLowerCase()

根据指定区域语言环境设置的大小写映射，返回字符串被转换为小写的格式。

```javascript
str.toLocaleLowerCase([locale, locale, ...])
```

locale 指明要转换成小写格式的特定语言区域，默认是**主机环境的当前区域设置**。 如果以一个数组 Array形式给出多个locales, 最合适的地区将被选出来应用。

在大多数情况下，该方法和调用 toLowerCase() 的结果相同，但是在某些区域环境中，比如土耳其语，它的大小写映射并不遵循在 Unicode 中的默认的大小写映射，因此会有一个不同的结果。

#### toLocaleUpperCase()

根据指定区域语言环境设置的大小写映射，返回字符串被转换为大写的格式。

#### trim()

删除字符串两端空白字符。

空白字符是指 space、tab、no-break space 等，以及所有行终止符字符（如 LF，CR等）。

#### trimEnd()（ES6）

删除字符串末端空白字符。trimRight() 是这个方法的别名。

#### trimStart()（ES6）

删除字符串开头空白字符。trimLeft() 是这个方法的别名。

#### toString()

返回 String 对象的字符串形式。

#### valueOf()

返回 String 对象的原始值。

## 数值类型（Number）

根据语言规范，JavaScript 采用 [**遵循 IEEE 754 标准的双精度 64 位格式**](broken://spaces/-M8fDLTBWl2H-MOzligj/pages/BImpcGcIq2G9iPGH1Af9#dan-jing-du-yu-shuang-jing-du-fu-dian-shu) 表示数字。——也就是说，在 JavaScript，除了 BigInt，其他数值都不是整数/整型。

在 JavaScript 中，一个看上去像是整数的数值，其实都是浮点数。比如，`1` 和 `1.0` 是同一个浮点数。

使用小数时，也要小心这种情况：

```javascript
0.1 + 0.2 = 0.30000000000000004
```

### [双精度浮点数的范围和精度](broken://spaces/-M8fDLTBWl2H-MOzligj/pages/BImpcGcIq2G9iPGH1Af9#dan-jing-du-yu-shuang-jing-du-fu-dian-shu)

JavaScript 的 Number 类型为 **双精度 IEEE 754 64位** 浮点类型。双精度浮点数的组件：

* **符号位（S）：** 第 1 位是正负数符号位（sign），0 代表正数，1 代表负数；
* **指数位（E）：** 中间的 8 位是指数（exponent），用来表示指数位数；
* **尾数位（M）：** 最后的 23 位是尾数（mantissa），表示浮点数有效数位数，超出的部分自动进一舍零。

**浮点数的范围是由指数位的位数来决定的。** 双精度浮点数指数位由 11 位表示，取值个数是 2^11=2048，范围是 \[0,2047]，去除 0 和 2047 这两种特殊情况，取值 \[1,2046]。根据 IEEE 754 标准约定，减去中间数 1023，指数的取值范围是 \[-1022,1023]，1023 就是最大指数。取值范围通常记为：

* **符号位（S）：** 第 1 位是正负数符号位（sign），0 代表正数，1 代表负数；
* **指数位（E）：** 中间的 11位是指数（exponent），用来表示指数位数；
* **尾数位（M）：** 最后的 52 位是尾数（mantissa），表示浮点数有效数位数。超出的部分自动进一舍零。

**浮点数的范围是由指数位的位数来决定的。**

```javascript
[-Number.MAX_VALUE, Number.MAX_VALUE]
[-1.7976931348623157×e^308, 1.7976931348623157×e^308]
```

**浮点数的数据精度取决于尾数位。** 双精度浮点数的尾位数是 52 位，还有一位隐含位，所以 JavaScript 能够准确表示的整数范围在 -2^53\~2^53之间（不含两个端点），超过这个范围，无法精确表示这个整数。

2^53 = 9007199254740992，一共 16，因此，**双精度浮点数的精度为 15\~16 位**。

```javascript
[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]
[-9007199254740991, 9007199254740991]
```

### 数值的进制

JavaScript 对整数提供四种进制的表示方法：十进制、十六进制、八进制、二进制。

* 十进制：没有前缀，取值数字 0\~9。
* 八进制：有前缀 0o 或 0O 的数值，或者有前导 0，取值数字 0\~7。
* 十六进制：有前缀 0x 或 0X 的数值，取值数字 0~~9 和 a~~f（不区分大小写）。
* 二进制：有前缀 0b 或 0B 的数值，取值数字 0 和 1 。

```javascript
Number('0b1111') // 15
Number('0o1111') // 585
Number('0x1111') // 4369

+'0b1111' // 15
+'0o1111' // 585
+'0x1111' // 4369
```

如果进制表达中出现不属于该进制的数字，就会报错。

```javascript
10000a   // 十进制
0o100008 // 八进制
0x10000g // 十六进制
0b100002 // 二进制
// Uncaught SyntaxError: Invalid or unexpected token
```

从 ES5 开始，在严格模式之中，八进制就不再允许使用前缀 0，ES6 进一步明确，要使用前缀 0o 或 0O。

通常来说，有前导 0 的数值会被视为八进制，但是如果前导 0 后面有数字 8 和 9，则该数值被视为十进制。

**注意： Numbeer.parseInt() 方法用于将其他进制转换为十进制。**

**注意： Number.prototype.toString() 方法用于将其他进制转换为指定进制。**

```javascript
// 其他进制转十进制
const x = '110';
parseInt(x, 2)  // 2进制转十进制   6
parseInt(x, 8)  // 8进制转十进制   72
parseInt(x, 32) // 32进制转十进制  1056

// 十进制转其他进制
const x = 97;
x.toString(8);  // 十进制转八进制  "141"
x.toString(16); // 十进制转十六进制  "61"
x.toString(32); // 十进制转三十二进制 "31"
```

### 特殊数值

#### 正零（+0）和负零（-0）

两者区别是 64 位浮点数表示法的符号位不同。

它们是等价的，几乎所有场合，正零和负零都会被当作正常的`0`。唯一有区别的场合是，+0 或 -0 当作分母，返回的值是不相等的。

```js
-0 === +0 // true
0 === -0 // true
0 === +0 // true

1/-0 // -Infinity
1/+0 // Infinity
```

#### NaN

NaN 是特殊的“非数字”值，主要出现在将字符串解析成数字或者一些数学函数的运算结果出错的场景。

```js
5 - 'x'       // NaN
0 / 0         // NaN
Math.sqrt(-1) // NaN
```

NaN 本质上仍是 Number：

```javascript
typeof NaN // 'number'
```

NaN 有以下特性：

* NaN 不等于任何值，包括它本身;
* NaN 在布尔运算时被当作 false；
* NaN 与任何数（包括它自己）的运算，得到的都是 NaN。

```javascript
NaN === NaN  // false
100 > NaN    // false
100 < NaN    // false
Boolean(NaN) // false
NaN + 32 // NaN
NaN - 32 // NaN
NaN * 32 // NaN
NaN / 32 // NaN
```

### Infinity

Infinity 表示 **无穷**，主要用在两种场景：一是正的数值太大，或负的数值太小；另一种是 非 0 数值除以 0。

```javascript
Math.pow(2, 1024)  // Infinity
-Math.pow(2, 1024) // -Infinity
1/0  // Infinity
1/-0 // -Infinity
```

Infinity 有以下特性：

* 0 乘 Infinity，返回 NaN；0 除以 Infinity，返回0；Infinity 除以 0，返回 Infinity；
* Infinity 加上或乘以 Infinity，返回 Infinity；
* Infinity 减去或除以 Infinity，返回 NaN；
* Infinity 与 null 运算，null 会转成 0，等同于与 0 的计算；
* Infinity 与 undefined 运算，返回 NaN。

```javascript
5 * Infinity // Infinity
5 - Infinity // -Infinity
Infinity / 5 // Infinity
5 / Infinity // 0

0 * Infinity // NaN
0 / Infinity // 0
Infinity / 0 // Infinity

Infinity + Infinity // Infinity
Infinity * Infinity // Infinity

Infinity - Infinity // NaN
Infinity / Infinity // NaN
```

### Number对象

Number 对象是一个表示数值的 Javascript 内置的标准对象，可作为构造函数使用，也可以作为将其它类型的值转为数值的函数。

```javascript
new Number(1)    // Number {1}

new Number('1')  // Number {1}
new Number('a')  // Number {NaN}
new Number([])   // Number {0}
new Number({})   // Number {NaN}
new Number(true) // Number {1}
```

如果传参不是数值，则先转换为数值；无法转换为数字，则返回 NaN。

### Number静态方法

#### isInteger()

判断给定的参数是否为整数。**注意：** NaN 和正负 Infinity 不是整数。

#### isSafeInteger()

判断给定的参数是否是一个安全整数，范围为 \[-2^53，2^53 ]（不包括端点）。

一个安全整数是指：可以准确地表示为一个 IEEE-754 双精度数字；没有舍入任何其他整数以适应 IEEE-754 表示的结果。

#### parseFloat()

解析一个参数（必要时先转换为字符串）并返回浮点数。

解析规则：

* 解析过程中遇到了正号（+）、负号（-）、数字（0-9）、小数点（.）、或者科学记数法中的指数（e 或 E）以外的字符，则忽略该字符以及之后的所有字符，返回当前已经解析到的浮点数。
* 第二个小数点的出现也会使解析停止。
* 参数首位和末位的空白符会被忽略。
* 参数字符串的第一个字符不能被解析成为数字，则返回 NaN。
* 可以解析并返回 Infinity。
* 解析 BigInt 为 Number，会丢失精度，因为末位 n 字符被丢弃。

该方法可以转换一个已经定义了 **toString 或者 valueOf** 方法的对象，其返回值和在调用 toString 或者 valueOf 方法结果上调用 parseFloat() 返回的值相同。

```javascript
parseFloat(3.14);        // 3.14
parseFloat('3.14');      // 3.14
parseFloat('  3.14  ');  // 3.14
parseFloat('314e-2');    // 3.14
parseFloat('0.0314E+2'); // 3.14
parseFloat('3.14some non-digit characters'); // 3.14
parseFloat({ toString: function() { return "3.14" } }); // 3.14

parseFloat("FF2"); // NaN
parseFloat(900719925474099267n); // 900719925474099300, 丢失精度
```

#### parseInt()

解析一个参数并返回指定基数的十进制整数。

```javascript
Number.parseInt(string[, radix])
```

radix 是一个介于 2\~36 之间的整数，表示被解析字符串的基数（数学数字系统中的基）。

**注意：** radix 的值并不是默认为 10。

解析规则：

* 参数不是字符串，则使用 ToString 抽象操作将其转换为字符串。
* 参数字符串允许前导和尾随空格。
* 解析可以理解正号（+）和负号（-）。
* 参数字符串的第一个字符不能转换为数字（非数字或者不是指定基数中的数字），返回 NaN。
* 解析遇到的字符不是指定基数中的数字，将忽略该字符以及所有后续字符，并返回到该点为止已解析的整数值。
* 对于会自动转为科学计数法的数字，其科学记数法中的指数（e 或 E）被视为字符串，会终止解析。因此当对非常大或非常小的数字解析时，会产生意外结果。
* 解析 BigInt 为 Number，会丢失精度，因为末位 n 字符被丢弃。
* radix 小于 2 或大于 36 ，返回 NaN。
* radix 是 0、NaN、Infinity、undefined 或未指定的，其解析规则如下：
  * 参数字符串以 0x 或 0X 开头，那么 radix 被假定为 16，字符串的其余部分被当做十六进制数去解析。
  * 参数字符串以 0 开头，那么 radix 被假定为 8 或 10，具体为哪一个取决于浏览器的实现。ECMAScript 5 规范不再允许 parseInt 函数的实现环境把以 0 字符开始的字符串作为八进制数值，但不是所有浏览器都支持。**因此，永远都要明确给出 radix 参数的值**。
  * 参数字符串以任何其他值开头，那么 radix 被假定为 10。

```javascript
parseInt(3.14);        // 3
parseInt('3.14');      // 3
parseInt('  3.14  ');  // 3
parseInt('314e-2');    // 3
parseInt('0.0314E+2'); // 0
parseInt("Hello")      // NaN
parseInt('3.14some non-digit characters'); // 3
parseInt({ toString: function() { return "3.14" } }); // 3

parseInt(0.00000000000434) // 4，相当于 parseInt(4.34e-12)
parseInt(4.7 * 1e22)   // 4，相当于 parseInt(4.7e+22)

parseInt("0xF", 16);  // 15
parseInt("+0xF", 16); // 15
parseInt("-0xF", 16); // -15
parseInt("0xF");      // 15
parseInt('015');      // 具体当十进制或八进制字符串解析，取决于浏览器实现
parseInt("3", 2);     // NaN
parseInt("8", 8);     // NaN
parseInt("08", 8);    // 0

parseInt("FF2"); // NaN
parseInt(900719925474099267n); // 900719925474099300, 丢失精度
```

**注意： parseInt 方法用于将其他进制转换为十进制。**

#### isFinite()（ES6）

检测传入的参数是否是一个有穷数。

与全局的 isFinite() 函数相比，该方法不会强制将一个非数值的参数转换成数值，这就意味着，只有数值类型的值，且是有穷的，才返回 true。

#### isNaN()（ES6）

判断给定的参数是否为 NaN。

与全局函数 isNaN() 相比，该方法不会自行将参数转换成数字，只有在参数是值为 NaN 的数字时，才会返回 true。

### Number属性

#### EPSILON（ES6）

两个可表示数之间的最小间隔，其值接近于 2.2204460492503130808472633361816E-16，或者 2^-52。

#### MAX\_SAFE\_INTEGER

JavaScript 中最大的安全整数（2^53 - 1）。安全数的意思是指能够准确区分两个不相同的值，比如，`Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2` 返回的是 true，这在数学上是错误的。

#### MIN\_SAFE\_INTEGER

JavaScript 中最小的安全整数（-(2^53 - 1)）。

#### MAX\_VALUE

能表示的最大正数，其值约为 1.79E+308，大于 MAX\_VALUE 的值代表 Infinity；最小的负数是 -MAX\_VALUE。

#### MIN\_VALUE

能表示的最小正数，其值约为 5e-324，小于 MIN\_VALUE 的值将会转换为 0；最大的负数是 -MIN\_VALUE。

#### NaN

特殊的“非数字”值。

#### NEGATIVE\_INFINITY

特殊的负无穷大值，在溢出时返回该值。

#### POSITIVE\_INFINITY

特殊的正无穷大值，在溢出时返回该值。

### Number实例方法

#### toExponential()

以指数表示法（科学记数法）返回该数值字符串表示形式。

```javascript
numObj.toExponential(fractionDigits)
```

fractionDigits 是一个整数，用来指定小数点后有几位数字，小数点后以 fractionDigits 提供的值来四舍五入。默认情况下用尽可能多的位数来显示数字。

fractionDigits 太小或太大将会抛出该错误，一般介于 0\~20（包括 20）之间的值不会引起 RangeError 。 执行环境也可以支持更大或更小范围。

数值使用该方法，数值与该方法之间隔开一个空格或点号，或者用小括号将数值包括，否则会报错：

```javascript
(1).toExponential()  // '1e+0'
(1).toExponential(5) // '1.00000e+0'
1..toExponential(5)  // '1.00000e+0'
1 .toExponential(5)  // '1.00000e+0'
1.toExponential(5)   // Uncaught SyntaxError: Invalid or unexpected token
```

#### toFixed()

返回一个指定小数位数的字符串。

```javascript
numObj.toFixed(digits)
```

digits 是小数点后数字的个数；介于 0\~20 （包括）之间，实现环境可能支持更大范围。如果忽略该参数，则默认为 0。

该数值在必要时进行四舍五入，另外在必要时会用 0 来填充小数部分，以便小数部分有指定的位数。 如果数值大于 1e+21，该方法会简单调用 toString() 并返回一个指数记数法格式的字符串。

**注意： 该方法的四舍五入是不安全的。**

```javascript
(12345.6789).toFixed()  // '12346'
(12345.6789).toFixed(1) // '12345.7'
(12345.6789).toFixed(6) // '12345.678900'

(1.23e+20).toFixed(2);  // '123000000000000000000.00'
(1.23e+21).toFixed(2);  // '1.23e+21'
(1.23e-10).toFixed(2);  // '0.00'

2.35.toFixed(1)         // '2.4'，正常四舍五入
2.55.toFixed(1)         // '2.5'，异常，四舍五入

-2.34.toFixed(1);       // -2.3，由于操作符优先级，负数不会返回字符串
(-2.34).toFixed(1);     // "-2.3"，若用括号提高优先级，则返回字符串
```

#### toLocaleString()

返回数值在特定语言环境下的表示字符串。

```javascript
numObj.toLocaleString([locales [, options]])
```

新的 locales 和 options 参数可以指定要进行格式转换的语言，并且定制函数的行为。在旧的实现中，会忽略 locales 和 options 参数，使用的语言环境和返回的字符串的形式完全取决于实现方式。

locales 是缩写语言代码的字符串或者这些字符串组成的数组。

locales 使用格式是 `language[-scripts][-region]-u-nu-*`，如，zh-u-nu-hanidec（表示中文十进制数字）。

nu 是使用的编号系统，可接收的值有：

```javascript
"arab", "arabext", "bali", "beng", "deva", "fullwide", "gujr", "guru", "hanidec"(中文十进制数字), "khmr", "knda", "laoo", "latn", "limb", "mlym", "mong", "mymr", "orya", "tamldec", "telu", "thai", "tibt".
```

> 关于参数 locales 的一般形式和解释请参见 [Intl](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl#Locale_identification_and_negotiation).
>
> Intl 对象是 ECMAScript 国际化 API 的一个命名空间，它提供了精确的字符串对比、数字格式化，和日期时间格式化。

options 包含一些或所有的下面属性的类:

* “decimal”：用于纯数字格式；
* “currency”：用于货币格式；
* “percent”：用于百分比格式；
* “localeMatcher”：使用的 local 的匹配算法，可能的值有：lookup | best fit，默认值是 best fit。
* “style”：要使用的格式样式，默认为 “decimal”。
* “useGrouping”：是否使用分组分隔符，如千位分隔符或千/万/亿分隔符。可能的值是：true|false，默认值是 true。

```javascript
(1000000).toLocaleString()     // '1,000,000'
(1000000).toLocaleString('en') // '1,000,000'

// 德国使用逗号作为小数分隔符，分位周期为千位
(123456.789).toLocaleString('de-DE'); // '123.456,789'

// 在大多数阿拉伯语国家使用阿拉伯语数字
(123456.789).toLocaleString('ar-EG'); // '١٢٣٬٤٥٦٫٧٨٩'

// 印度使用千位/拉克（十万）/克若尔（千万）分隔
(123456.789).toLocaleString('en-IN'); // '1,23,456.789'

// 中文十进制
(123456.789).toLocaleString('zh-Hans-CN-u-nu-hanidec'); // '一二三,四五六.七八九'

// 当请求不支持的语言时，例如巴厘语，加入一个备用语言，比如印尼语
(123456.789).toLocaleString(['ban', 'id']); // '123.456,789'
```

```javascript
// 中国人民币
(123456.789).toLocaleString('zh', { style: 'currency', currency: 'CNY' });
// '¥123,456.79'
(123456.789).toLocaleString('zh', { style: 'currency', currency: 'CNY', currencyDisplay: 'code' });
// 'CNY 123,456.79'
(123456.789).toLocaleString('zh', { style: 'currency', currency: 'CNY', currencyDisplay: 'name' });
// '123,456.79人民币'

// 要求货币格式
(123456.789).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' });
// '123.456,79 €'

// 日元不使用小数位
(123456.789).toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' });
// '￥123,457'

// 限制三位有效数字
(123456.789).toLocaleString('en-IN', { maximumSignificantDigits: 3 });
// '1,23,000'
```

#### toPrecision()

返回指定有效数个数的整数的字符串表示。

```javascript
numObj.toPrecision(precision)
```

precision 是一个整数，介于 1\~100 （包括）之间，超出将会抛出一个 RangeError 错误 ，执行环境也可以支持更大或更小的范围。ECMA-262 只需要最多 21 位显示数字。

该方法以定点表示法或指数表示法表示的一个数值的字符串，四舍五入到 precision 参数指定的显示数字位数。**注意：** 类似 toFixed() 方法，其四舍五入也是不安全的。

忽略 precision 参数，则该方法表现类似于 toString()，如果该参数是一个非整数值，将会向下舍入到最接近的整数。

```javascript
(5.123456).toPrecision()  // '5.123456'
(5.123456).toPrecision(5) // '5.1235'
(5.123456).toPrecision(2) // '5.1'
(5.123456).toPrecision(1) // '5'

(5123456).toPrecision(1)  // '5e+6'
(5123456).toPrecision(3)  // '5.12e+6'
(5123456).toPrecision(7)  // '5123456'

(5123456).toPrecision(100)
//'5123456.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
(5123456).toPrecision(101)
// Uncaught RangeError: toPrecision() argument must be between 1 and 100
```

#### toString()

返回指定数值的字符串表示形式。

```javascript
numObj.toString([radix])
```

radix 是指定要用于数字到字符串的转换的基数，介于2\~36之间，超出将会抛出一个 RangeError 错误，默认值为 10。

```javascript
(10).toString()     // '10'
(10).toString(2)    // '1010'
(10).toString(8)    // '12'
(10).toString(16)   // 'a'
(0x10).toString(2)  // '10000'
(0o10).toString(16) // '8'
```

**注意： toString() 方法用于将其他进制转换为指定进制。**

## 布尔类型（Boolean）

在计算机科学中，布尔值是一种取值仅能为真或假的数据类型，它赋予了编程语言在逻辑上表达真或假的能力。布尔类型有两个值：true 和 false。

### Boolean对象

Boolean 是一个表示布尔值的 Javascript 标准内置对象，其构造函数可以用来创建一个布尔值：

```javascript
let b = new Boolean();
```

如果需要，可以给构造函数传一个参数，传递的值将转换为布尔值。如果省略或值 0、-0、null、false、NaN、undefined、或空字符串（""），该对象具有的初始值 false。所有其他值，包括任何对象、空数组（\[]）或字符串 "false"，都会创建一个初始值为 true 的对象。

**需要注意的是：** 不要将基本类型中的布尔值 true 和 false，与值为 true 和 false 的 Boolean 对象弄混了。

```javascript
let b = new Boolean(false);
if (b) { // 条件判断为 true
	...
}
```

```javascript
let b = false;
if (b) { // 条件判断为 false
	...
}
```

### Boolean的方法

#### toString()

根据对象的值返回字符串 "true" 或 “false”。

#### valueOf()

返回 Boolean 对象的原始值，即 true 或 false。

### 布尔值的运算

下列运算符会返回布尔值：

* 逻辑运算符： &&、||、 !、！！
* 相等运算符：===、!==、==、!=
* 比较运算符：>、>=、<、<=

## undefined

undefined 是 JavaScript 基本类型之一，是一个未定义类型的对象，表示一个未初始化的值，也就是还没有被分配的值。它是全局对象的一个属性，也就是说，它是全局作用域的一个变量。

JavaScript 允许声明变量但不对其赋值，一个未被赋值的变量就是 undefined 类型：

```javascript
let undef;
console.log(undef) // undefined
```

方法或者语句中操作的变量没有被赋值，则会返回 undefined：

```javascript
function test(a){
    console.log(typeof a); // undefined
}
test()
```

一个函数如果没有使用 return 语句指定返回值，则会返回 undefined：

```javascript
function test(){}
test()
```

**注意：** undefined 可以在非全局作用域中被当作标识符（变量名）来使用（undefined 不是一个保留字），但，这样并不是好的主意，因为这样会使代码难以去维护和排错。

```javascript
function test () {
    let undefined = 'foo';
    console.log(undefined, typeof undefined)
}
test() // foo string

function test (undefined) {
    console.log(undefined, typeof undefined)
}
test('foo') // foo string
```

**注意：** typeof 方法判断一个变量是否为 undefined，原因是，typeof 不会在一个变量没有被声明的时候抛出一个错误。

```javascript
if(typeof a === 'undefined') {
   console.log('变量a类型为：', typeof a) // 变量a类型为： undefined
}

if(a === undefined) {} // Uncaught ReferenceError: a is not defined
```

**void 操作符：** `void expression` 对给定的表达式进行求值，并无论结果是什么，都返回 undefined。

```javascript
void 0;                    // undefined  
void (false);              // undefined  
void {name: 'John Smith'}; // undefined  
void Math.min(1, 3);       // undefined  
```

**备注：** 一般可以用 `void 0` 来获得 undefined。

## null

null 也是 JavaScript 基本类型之一，只有一个值：nul，特指对象的值未设置，可以理解为尚未创建的对象。

在计算机学科， null 值一直是一个被讨论点，通常来说，表示一个不存在或者无效对象或者地址引用。

**注意：** 各语言实现中，null 的含义各不相同。

**注意：** null 是一个字面量，不是全局对象的一个属性。

## Symbol

symbol 是 Javascript 的一种基本类型。Symbol() 函数会返回 symbol 类型的值，该类型具有静态属性和静态方法。

```javascript
const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');

console.log(typeof symbol1); // symbol
console.log(symbol2 === 42); // false
console.log(symbol3.toString()); // Symbol(foo)
console.log(Symbol('foo') === Symbol('foo')); // false
```

```javascript
typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
```

每个从 Symbol() 返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符；这是该数据类型仅有的目的。

**注意：** symbol 不能做为构造函数，它不支持语法 `new Symbol()`。

```javascript
new Symbol() // Uncaught TypeError: Symbol is not a constructor
```

### 在对象中查找 Symbol 属性

Symbol 作为对象属性名时，不会出现在 for...in、for...of 循环中，也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。

```javascript
const object1 = {
    a: 1,
    [Symbol('b')]: 2
} 
Object.keys(object1) // ['a']
Object.getOwnPropertyNames(object1) // ['a']
JSON.stringify(object1) // '{"a":1}'
```

Object.getOwnPropertySymbols() 方法返回一个数组，成员给定对象的所有 Symbol 类型属性名。

```javascript
Object.getOwnPropertySymbols(object1) // [Symbol(b)]
```

### Symbol属性

#### length

长度属性，值为 0。

#### iterator

一个返回一个对象默认迭代器的方法。Symbol.iterator 是一个对象默认的迭代器。当对一个对象进行迭代时，它的 @@iterator 方法都会在不传参情况下被调用，返回的迭代器用于获取要迭代的值。

一些内置类型拥有默认的迭代器行为：

```javascript
Array.prototype[@@iterator]()
TypedArray.prototype[@@iterator]()
String.prototype[@@iterator]()
Map.prototype[@@iterator]()
Set.prototype[@@iterator]()
```

**注意：** Object 没有默认的迭代器。

```javascript
Array.prototype[Symbol.iterator]  // ƒ values() { [native code] }
Object.prototype[Symbol.iterator] // undefined
```

也就是说，Object 不能使用 for...of 循环。

```javascript
const obj = {
    a: 1,
    b: 2
}
for (let value of obj) {
    console.log(value)
}
// Uncaught TypeError: obj is not iterable
```

可以通过 Symbol.iterator 为对象添加自定义迭代器：

```javascript
obj[Symbol.iterator] = function * () {
    yield objIterator.a;
    yield objIterator.b;
};
for (let value of obj) {
    console.log(value)
}
// 1
// 2
```

如果一个迭代器 @@iterator 没有返回一个迭代器对象，那么它就是一个不符合标准的迭代器，这样的迭代器将会在运行期抛出异常，甚至非常诡异的 Bug。

```javascript
obj[Symbol.iterator] = () => 1;
for (let value of obj) {
    console.log(value)
}
// Result of the Symbol.iterator method is not an object
```

#### match

返回或指定一个字符串进行匹配的方法，也用于确定一个对象是否可以作为正则表达式使用。String.prototype.match() 方法会调用此方法。

```javascript
const regexp1 = /foo/;
console.log(regexp1[Symbol.match]);
// ƒ [Symbol.match]() { [native code] }

console.log('/foo/'.startsWith(regexp1));
// First argument to String.prototype.startsWith must not be a regular expression
```

Symbol.match 还用于标识对象是否具有正则表达式的行为。比如， String.prototype.startsWith()，String.prototype.endsWith()、String.prototype.includes() 这些方法会检查其第一个参数是否是正则表达式，是正则表达式就抛出一个 TypeError。

现在，如果 Symbol.match 设置为 false，就表示该对象不打算用作正则表达式对象。

```javascript
regexp1[Symbol.match] = false
console.log('/foo/'.startsWith(regexp1)); // true
```

#### replace

指定一个字符串替换所匹配字符串时所调用的方法。String.prototype.replace() 方法会调用此方法。

```javascript
const str = 'abcde';
console.log(str.replace('a', '1')) // 1bcde

class Replace1 {
    constructor(value) {
        this.value = value;
    }
    [Symbol.replace](string) {
        return `${string} -> ${this.value}`;
    }
}
console.log(str.replace(new Replace1('1'))) // abcde -> 1
```

#### search

指定一个字符串搜索所匹配字符串时所调用的方法。String.prototype.search() 方法会调用此方法。

```javascript
var str = 'abcde';
console.log(str.search('a')) // 0

class search1 {
    constructor(value) {
        this.value = value;
    }
    [Symbol.search](string) {
        return 100;
    }
}
console.log(str.search(new search1('a'))) // 100
```

#### split

指定一个正则表达式的索引处分割字符串的方法。 String.prototype.split() 方法会调用此方法。

#### hasInstance

指定用于判断某对象是否为某构造器的实例。instanceof 会调用此方法。

#### isConcatSpreadable

用于配置某对象作为 Array.prototype.concat() 方法的参数时是否展开其数组元素。

```javascript
const a = ['a', 'b', 'c'];
const b = [1, 2, 3];
console.log(a.concat(b)); //  ['a', 'b', 'c', 1, 2, 3]

b[Symbol.isConcatSpreadable] = false;
console.log(a.concat(b)); // ['a', 'b', 'c', Array(3)]
```

#### unscopables

指定对象中的属性，是否从 with 环境绑定中排除。

```javascript
const a = {
    b: 1,
    c: 2
}
with (a) {
    console.log(b) // 1
    console.log(c) // 2
}

a[Symbol.unscopables] = {
    b: false,
    c: true
};
with (a) {
    console.log(b) // 1
    console.log(c) // Uncaught ReferenceError: c is not defined
}
```

#### species

一个用于创建派生对象的构造器函数，允许子类覆盖对象的默认构造函数。

```javascript
class MyArray extends Array {
    static get [Symbol.species]() { return Array; }
}

const a = new MyArray(1, 2, 3);
console.log(a instanceof MyArray); // true
console.log(a instanceof Array);   // true

const mapped = a.map(x => x * x);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array);   // true
```

#### toPrimitive

指定一个对象转换为对应的原始值时调用的函数。该函数被调用时，会被传递一个字符串参数 hint ，表示要转换到的原始值的预期类型，可接收值：number | string | default。

```javascript
const obj = {
    a: 1
};
console.log(+obj);     // NaN
console.log(`${obj}`); // [object Object]
console.log(obj + ''); // [object Object]

obj[Symbol.toPrimitive] = (hint) => {
    if (hint == "number") { return 10; }
    if (hint == "string") { return "hello"; }
    return true;
};
console.log(+obj);     // 10
console.log(`${obj}`); // hello
console.log(obj + ''); // true
```

转换为原始值的场景？？hint 参数取决于什么？？

#### toStringTag

指定对象的方法，返回对象所属的类型。Object.prototype.toString() 方法会调用此方法。

一些对象类型即便没有 toStringTag 属性，toString() 方法也能返回特定的类型标签：

```javascript
Object.prototype.toString.call('foo');  // [object String]
Object.prototype.toString.call([1, 2]); // [object Array]
Object.prototype.toString.call(3);      // [object Number]
```

另外一些对象类型则不然，toString() 方法能识别它们，是因为引擎设置好了 toStringTag 标签：

```javascript
console.log(Object.prototype.toString.call(new Map()));         // [object Map]
console.log(Object.prototype.toString.call(Promise.resolve())); // [object Promise]
```

此外，自定义的类加上 toStringTag 属性，也可以有自定义的类型标签：

```javascript
class MyClass {
    get [Symbol.toStringTag]() {
        return "MyClass";
    }
}
console.log(Object.prototype.toString.call(new MyClass())); // [object MyClass]
```

### Symbol方法

#### for(key)

根据给定的键 key，来从运行时的 Symbol 注册表中找到对应的 Symbol，如果找到了，则返回它，否则，新建一个与该键关联的 Symbol，并放入全局 Symbol 注册表中。

```javascript
Symbol.for(key);
```

key 是一个字符串，作为 Symbol 注册表中与某 Symbol 关联的键，同时也会作为 Symbol 的描述。

```javascript
Symbol.for("foo"); // Symbol(foo)，创建一个新 Symbol
Symbol.for("foo"); // Symbol(foo)，获取已创建 Symbol

Symbol.for("bar") === Symbol.for("bar"); // true
Symbol("bar") === Symbol("bar");         // false
```

#### keyFor(sym)

获取全局 Symbol 注册表中与某个 Symbol 关联的键。

```javascript
const globalSym = Symbol.for("foo");
Symbol.keyFor(globalSym); // 'foo'

const localSym = Symbol();
Symbol.keyFor(localSym); // undefined
```

### Symbol实例属性

#### description

一个只读的字符串，意为对该 Symbol 对象的描述。

```javascript
const symbol1 = Symbol(100);
console.log(symbol1.description) // 100
```

### Symbol实例方法

#### toSource（非标准）

返回该 Symbol 对象的源代码。

#### toString

返回一个包含着该 Symbol 对象描述的字符串。

#### valueOf

返回该 Symbol 对象。

#### \[@@toPrimitive]

返回该 Symbol 对象。

在 JavaScript 中，虽然大多数类型的对象在某些操作下都会自动的隐式调用自身的 valueOf() 方法或者 toString() 方法来将自己转换成一个原始值，但 symbol 对象不会这么干，symbol 对象无法隐式转换成对应的原始值。

```javascript
const symbol1 = Symbol(100);
console.log(symbol1.toString()) // 'Symbol(100)'
console.log(symbol1.valueOf())  // Symbol(100)

console.log(symbol1.toSource())
// Uncaught TypeError: symbol1.toSource is not a function
console.log(symbol1[@@toPrimitive]())
// Uncaught SyntaxError: Invalid or unexpected token
```

### Symbol类型转换

当使用 Symbol 值进行类型转换时需要注意一些事情：

* Symbol 值转换为 Number 值时，会抛出一个 TypeError 错误。
* Symbol 值**隐式**转换为 String 值时，会抛出一个 TypeError 错误，比如，。
* Object(sym) == sym，返回 true。

```javascript
const symbol1 = Symbol(100);

console.log(+symbol1)
console.log(Number(symbol1))
// Uncaught TypeError: Cannot convert a Symbol value to a number

console.log(symbol1 + '') // TypeError: Cannot convert a Symbol value to a string
String(symbol1)           // 'Symbol(100)'，类似 symbol1.toString()

Object(symbol1) == symbol1  // true
Object(symbol1) === symbol1 // false
```

## BigInt

ECMAScript 标准定义了两种内建数值类型：Numbe 和 BigInt。

BigInt 是 JavaScript 的基本类型之一，可以表示任意大的整数，可以安全地存储和操作大整数，甚至可以超过数字类型的安全整数（2^53 - 1）限制。

BigInt 是通过在整数末尾附加字母 n 或调用构造函数来创建的。

```javascript
const bigint1 = 9007199254740991n;
const bigint2 = BigInt(9007199254740991);
const bigint3 = BigInt('9007199254740991');
const bigint4 = BigInt("0x1fffffffffffff");
const bigint5 = BigInt("0b11111111111111111111111111111111111111111111111111111");
// 9007199254740991n
```

Number.MAX\_SAFE\_INTEGER 表示最大的安全数值，而通过引入 BigInt，可以操作超过该值的数值。

```javascript
Number.MAX_SAFE_INTEGER + 1 // 9007199254740992
Number.MAX_SAFE_INTEGER + 2 // 9007199254740992
Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 // true

const bigint = BigInt(Number.MAX_SAFE_INTEGER); // 9007199254740991n
bigint + 1n === bigint + 2n; // false
```

### BigInt特性

* BigInt 可以使用运算符：+、-、\*、 \*\*、%，除 `>>>` （无符号右移）之外的 位操作也可以支持。因为 BigInt 都是有符号的，`>>>` 不能用于 BigInt。
* / 操作符对于 BigInt 的运算也没问题。可是因为这些变量是 BigInt 而不是 BigDecimal ，该操作符结果会向零取整，也就是说**不会返回小数部分**。
* 为了兼容 asm.js，BigInt 不支持单目（+）运算符。
* BigInt 不能使用 Math 对象中的方法。
* BigInt 不能和任何 Number 混合运算，两者必须转换成同一种类型。
* BigInt 可以与 Number 进行比较。
* BigInt 数据转换：BigInt 转换成 Number，可能会丢失精度；BigInt 转换成 String，末尾 n 会丢失；BigInt 转换 Boolean，或者进行 if 语句判断，或者 ||、&&、! 操作时，行为类似于一个数字。

  ```javascript
  let bigint = BigInt(100)

  bigint + 1n // 101n
  bigint / 6n // 16n
  ++ bigint   // 101n

  1n == 1  // false
  1n === 1 // true
  1n < 2   // true

  // 可以混在一个数组内并排序
  const num = [4n, 6, -12n, 10, 4, 0, 0n];
  num.sort() // [-12n, 0, 0n, 10, 4n, 4, 6]

  Number(bigint)     // 101
  String(bigint)     // '101'
  Boolean(BigInt(0)) // false
  Boolean(BigInt(1)) // true

  +bigint          // Uncaught TypeError: Cannot convert a BigInt value to a number
  Math.abs(bigint) // Uncaught TypeError: Cannot convert a BigInt value to a number
  bigint + 1       // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
  ```
* 对任何 BigInt 值使用 JSON.stringify() 都会引发 TypeError，因为默认情况下 BigInt 值不会在 JSON 中序列化。

  ```javascript
  const json = {
      a: BigInt(100)
  }
  console.log(JSON.stringify(json))
  // Uncaught TypeError: Do not know how to serialize a BigInt
  ```

  但是，如果需要，可以实现 toJSON 方法：

  ```javascript
  BigInt.prototype.toJSON = function () { return this.toString(); }
  ```

  ```javascript
  console.log(JSON.stringify(json)) // {"a":"100"}
  ```

### BigInt对象

JavaScript 提供 BigInt 函数，用于创建 BigInt 类型的数值。**注意：** BigInt 不能当作构造函数使用。

```javascript
new BigInt(100) // Uncaught TypeError: BigInt is not a constructor
BigInt(100)     // 100n
```

BigInt 函数必须有参数，而且参数必须可以正常转为数值：

```javascript
BigInt(100n)      // 100n
BigInt(undefined) // TypeError: Cannot convert undefined to a BigInt
BigInt(null)      // Uncaught TypeError: Cannot convert null to a BigInt
BigInt("100n")    // Uncaught SyntaxError: Cannot convert 100n to a BigInt
BigInt('abc')     // Uncaught SyntaxError: Cannot convert abc to a BigInt
```

参数如果是小数，也会报错：

```javascript
BigInt(100.1)   // Uncaught RangeError: The number 100.1 cannot be converted to a BigInt because it is not an integer
BigInt('100.1') // Uncaught SyntaxError: Cannot convert 100.1 to a BigInt
```

### BigInt静态方法

#### asIntN()

将 BigInt 值转换为一个 -2^(width-1) 与 2^(width-1) - 1 之间的有符号整数。

#### asUintN()

将一个 BigInt 值转换为 0 与 2^(width) - 1 之间的无符号整数。

width 是可存储整数的位数，bigint 是要存储在指定位数上的整数。

```javascript
const max = 2n ** (64n - 1n) - 1n;
BigInt.asIntN(64, max);      // 9223372036854775807n
BigInt.asIntN(64, max + 1n); // -9223372036854775808n. 负数，因为已经溢出

const uMax = 2n ** 64n - 1n;
BigInt.asUintN(64, uMax);      // 18446744073709551615n
BigInt.asUintN(64, uMax + 1n); // 0n. 0，因为已经溢出
```

### BigInt实例方法

#### toLocaleString()

返回此数字的 language-sensitive 形式的字符串。类似 Number.prototype.toLocaleString() 方法。

```javascript
const bigint = 123456789123456789n;
bigint.toLocaleString('de-DE');
// '123.456.789.123.456.789'
bigint.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' });
// '123.456.789.123.456.789,00 €'
```

#### toString()

返回以指定基数(base)表示指定数字的字符串。类似 Number.prototype.toString() 方法。

```javascript
1024n.toString();   // 1024
1024n.toString(2);  // 10000000000
1024n.toString(16); // 400
```

#### valueOf()

返回指定对象的原始值。

```javascript
1024n.valueOf();  // 1024n
typeof Object(1n).valueOf() // 'bigint'
```

## 相关问题

### parseInt、+ 解析字符串中数值的区别

parseInt() 和 parseFloat() 会尝试逐个解析字符串中的字符，直到遇上一个无法被解析成数字的字符，然后返回该字符前所有数字字符组成的数字。

但是运算符 "+" 对字符串的转换方式与之不同，其方式类似 Number() 函数，即，只要字符串含有无法被解析成数字的字符，该字符串就将被转换成 `NaN`。

```javascript
parseInt('10.2abc')   // 10
parseFloat('10.2abc') // 10.2
+ '10.2abc'           // NaN
```

### 字符串转数值

* **parseInt(string, num)：** 解析一个参数并返回指定基数的十进制整数。
* **parseFloat(string)：** 解析一个参数（必要时先转换为字符串）并返回浮点数。
* **Number(param)：** 如果传参不是数值，则先转换为数值；无法转换为数字，则返回 NaN。
* **加（+）运算：** 数字字符串前加加号（+），转换成整数、浮点数。如：`+ "12"`、`+ 12.1`。
* **乘（\*）运算**：数字字符串乘（\*）1，转换成数值，可为整数、浮点数。如：`"12" * 1`、`12.1 * 1`。
* **\~\~ 运算：** 位运算符 `~` 原意是按位取反，使用 `~~` 可以将一个\*\*数字字符串转化成整数。\*\*如：`~~'1223'`。
* **>> 运算：** 位运算符 `>>` 原意是带符号右移，使用 `>>` 0 可以将一个\*\*数字字符串转化成整数。\*\*如：`12>>0`

### 数字千位分隔符

```js
(101102298.29382).toLocaleString('en')。
//'en'，必需加上，也为小写，否则会有兼容性问题

('101102298.29382').replace(/(?!^)(?=(\d{3})+\.)/g, ",")
```

### \[1, 2, 3].map(parseInt) 答案是多少？

parseInt() 方法用于解析一个字符串参数并返回一个指定的整数基数（数学数字系统的基础）。

```javascript
parseInt(string, radix)
```

string 表示待解析的值。如果不是字符串，则使用 `ToString` 抽象操作。

radix 表示一个介于2 \~ 36 之间的整数，表示整数的基数（数学数字系统中的基数） 。如果基数值不是 Number 类型，它将被强制转换为 Number。

radix 小于 2 或大于 36 ，返回 NaN。

parseInt 解析遇到的字符不是指定基数中的数字，将忽略该字符以及所有后续字符，并返回到该点为止已解析的整数值。

此处 map 传了 3 个参数 (element, index, array)，默认第三个参数被忽略掉，实际等价于：

```javascript
['1', '2', '3'].map((element, index) => parseInt(element, index))

// 相当于依次执行：
parseInt('1', 0) // 未指定 radix，且是以1开头，radix = 10。返回 1
parseInt('2', 1) // radix 小于 2 或大于 36 ，返回 NaN
parseInt('3', 2) // radix = 2，表示二进制，但二进制只允许出现 0、1。返回 NaN
```

最终返回结果：\[1, NaN, NaN]。

### 什么是假值对象？

浏览器在某些特定情况下，在常规 JavaScript 语法基础上自己创建了一些外来值，这些就是“假值对象”。假值对象看起来和普通对象并无二致（都有属性，等等），但将它们强制类型转换为布尔值时结果为 false。最常见的例子是 `document.all`，它是一个类数组对象，包含了页面上的所有元素，由 DOM（而不是 JavaScript 引擎）提供给 JavaScript 程序使用。

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

### undefined 和 undeclared 的区别？

undefined 表示作用域中**声明但未赋值**的变量；undeclared 表示作用域中**未声明的变量**。

```javascript
var a
console.log(a) // undefined，变量 a 声明但未赋值

console.log(b) // Uncaught ReferenceError: b is not defined，变量 b 未声明。
```

typeof 的安全防范机制，可以避免 undeclared 变量报错：

```javascript
console.log(typeof b) // 'undefined'
```

### undefined 和 null 的区别？

1995年，JavaScript诞生时，最初像 Java 一样，只设置了 null 作为表示"无"的值。但是，JavaScript 的设计者 Brendan Eich，觉得这样做还不够，有两个原因：

* null 像在 Java 里一样，被当成一个对象。但是，JavaScript 的数据类型分成原始类型和对象类型，Brendan Eich 觉得表示 **无** 的值最好不是对象。
* 其次，JavaScript 的最初版本没有包括错误处理机制，发生数据类型不匹配时，往往是自动转换类型或者默默地失败。Brendan Eich觉得，如果 null 自动转为 0，很不容易发现错误。

因此，Brendan Eich又设计了一个 undefined。

null 与 undefined 都可以表示 **没有**，含义非常相似。

undefined 表示 **未定义**，是一个表示 **无** 的原始值。

null 表示空值，是一个表示 **无** 的对象，一般用于赋值给一些可能会返回对象的变量，作为初始化。

两者的主要差异在于：

* 数据类型不同。

  ```javascript
  typeof null        // 'object'
  typeof undefined   // "undefined"
  undefined == null  // true
  undefined === null // false
  ```
* 转换为其他类型时，两者的返回结果可能不同。

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

  String(null)       // 'null'
  String(undefined)  // 'undefined'

  Boolean(null)      // false
  Boolean(undefined) // false
  ```
* undefined 是一个全局变量，不是保留字，而 null 不是局属变量，但是保留字。

  ```javascript
  null = 1      // Uncaught SyntaxError: Invalid left-hand side in assignment
  undefined = 1 // 1

  function test () {
      var undefined = 1
      console.log(undefined) // 1

      var null = 1 // Uncaught SyntaxError: Unexpected token 'null'
  }
  test()
  ```

### 为什么 void 获取 undefined 更安全？

void 运算符对给定的表达式进行求值，不管结果是什么，都会返回 undefined。

undefined **不是 Javascript 的保留字**，属于基础类型，同时还是全局属性，换言之，undefined 是可以被重写。

```javascript
window.hasOwnProperty('undefined')   //true
window.undefined //undefined
window.undefined = 1 // ES5以后，全局属性 undefined 已不允许修改
```

**注意：** 根本原因在于 ECMAScript 5 之前，undefined 作为全局属性，是可读写，某些质量不高的库会覆盖 undefined 的值，导致直接赋值 undefined 不符合预期，在 ECMAScript 5 之后，undefined 设置成只读，也就不存在被重写问题，兼容性方面也基本不存在太大问题。但是在局部作用域中，还是可以被重写的。

```javascript
function test (a) {
    var undefined = 1
    console.log(a === undefined) // false
    console.log(a === void 0)    // true
}
test()
```

另一种安全的写法是可以用 typeof：对于原始值 undefined ， typeof 始终返回字符串 'undefined'。

```javascript
function test (a) {
    console.log(typeof a === 'undefined') // true
}
test()
```

### 什么情况下，数值以科学计数法显示？

科学记数法，又称科学记法，是一种数字的表示法，一个数被写成一个实数 a 与一个 10 的 n 次幂的积。比如，12340000，可以记为 1.234×10^7，表示为 1.234e+7。

以下两种情况，JavaScript 会自动将数值转为科学计数法表示：

* 小于 1 且小数点后面有连续的 0 超过5 个。
* 整数位数字多于 21 位。

```javascript
123456789012345678901  // 123456789012345680000
1234567890123456789012 // 1.2345678901234568e+21
0.000001  // 0.000001
0.0000001 // 1e-7
```

## 参考资料

[MDN - JavaScript 数据类型和数据结构](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Data_structures)

[MDN - 数字和日期](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Numbers_and_dates)

[MDN - Text formatting](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Text_formatting)

[阮一峰 - JavaScript 标准参考教程（alpha）](https://javascript.ruanyifeng.com/)
