mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2025-05-24 10:22:23 +00:00
docs: consistent style
This commit is contained in:
parent
eeba1984a8
commit
e8fdb00040
@ -234,7 +234,7 @@ const [first, ...middle, last] = [1, 2, 3, 4, 5];
|
||||
[...'x\uD83D\uDE80y'].length // 3
|
||||
```
|
||||
|
||||
上面代码的第一种写法,JavaScript 会将四个字节的 Unicode 字符,识别为2个字符,采用扩展运算符就没有这个问题。因此,正确返回字符串长度的函数,可以像下面这样写。
|
||||
上面代码的第一种写法,JavaScript 会将四个字节的 Unicode 字符,识别为 2 个字符,采用扩展运算符就没有这个问题。因此,正确返回字符串长度的函数,可以像下面这样写。
|
||||
|
||||
```javascript
|
||||
function length(str) {
|
||||
@ -322,7 +322,7 @@ let arr = [...obj]; // TypeError: Cannot spread non-iterable object
|
||||
|
||||
## Array.from()
|
||||
|
||||
`Array.from`方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
|
||||
`Array.from`方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
|
||||
|
||||
下面是一个类似数组的对象,`Array.from`将它转为真正的数组。
|
||||
|
||||
@ -341,7 +341,7 @@ var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
|
||||
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
|
||||
```
|
||||
|
||||
实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的`arguments`对象。`Array.from`都可以将它们转为真正的数组。
|
||||
实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的`arguments`对象。`Array.from`都可以将它们转为真正的数组。
|
||||
|
||||
```javascript
|
||||
// NodeList对象
|
||||
@ -359,7 +359,7 @@ function foo() {
|
||||
|
||||
上面代码中,`querySelectorAll`方法返回的是一个类似数组的对象,可以将这个对象转为真正的数组,再使用`forEach`方法。
|
||||
|
||||
只要是部署了Iterator接口的数据结构,`Array.from`都能将其转为数组。
|
||||
只要是部署了 Iterator 接口的数据结构,`Array.from`都能将其转为数组。
|
||||
|
||||
```javascript
|
||||
Array.from('hello')
|
||||
@ -369,7 +369,7 @@ let namesSet = new Set(['a', 'b'])
|
||||
Array.from(namesSet) // ['a', 'b']
|
||||
```
|
||||
|
||||
上面代码中,字符串和Set结构都具有Iterator接口,因此可以被`Array.from`转为真正的数组。
|
||||
上面代码中,字符串和 Set 结构都具有 Iterator 接口,因此可以被`Array.from`转为真正的数组。
|
||||
|
||||
如果参数是一个真正的数组,`Array.from`会返回一个一模一样的新数组。
|
||||
|
||||
@ -418,7 +418,7 @@ Array.from([1, 2, 3], (x) => x * x)
|
||||
// [1, 4, 9]
|
||||
```
|
||||
|
||||
下面的例子是取出一组DOM节点的文本内容。
|
||||
下面的例子是取出一组 DOM 节点的文本内容。
|
||||
|
||||
```javascript
|
||||
let spans = document.querySelectorAll('span.name');
|
||||
@ -458,7 +458,7 @@ Array.from({ length: 2 }, () => 'jack')
|
||||
|
||||
上面代码中,`Array.from`的第一个参数指定了第二个参数运行的次数。这种特性可以让该方法的用法变得非常灵活。
|
||||
|
||||
`Array.from()`的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种Unicode字符,可以避免JavaScript将大于`\uFFFF`的Unicode字符,算作两个字符的bug。
|
||||
`Array.from()`的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种 Unicode 字符,可以避免 JavaScript 将大于`\uFFFF`的 Unicode 字符,算作两个字符的 bug。
|
||||
|
||||
```javascript
|
||||
function countSymbols(string) {
|
||||
@ -484,7 +484,7 @@ Array(3) // [, , ,]
|
||||
Array(3, 11, 8) // [3, 11, 8]
|
||||
```
|
||||
|
||||
上面代码中,`Array`方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于2个时,`Array()`才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
|
||||
上面代码中,`Array`方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于 2 个时,`Array()`才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
|
||||
|
||||
`Array.of`基本上可以用来替代`Array()`或`new Array()`,并且不存在由于参数不同而导致的重载。它的行为非常统一。
|
||||
|
||||
@ -516,7 +516,7 @@ Array.prototype.copyWithin(target, start = 0, end = this.length)
|
||||
它接受三个参数。
|
||||
|
||||
- target(必需):从该位置开始替换数据。
|
||||
- start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
|
||||
- start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
|
||||
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
|
||||
|
||||
这三个参数都应该是数值,如果不是,会自动转为数值。
|
||||
@ -526,7 +526,7 @@ Array.prototype.copyWithin(target, start = 0, end = this.length)
|
||||
// [4, 5, 3, 4, 5]
|
||||
```
|
||||
|
||||
上面代码表示将从3号位直到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2。
|
||||
上面代码表示将从 3 号位直到数组结束的成员(4 和 5),复制到从 0 号位开始的位置,结果覆盖了原来的 1 和 2。
|
||||
|
||||
下面是更多例子。
|
||||
|
||||
@ -563,7 +563,7 @@ i32a.copyWithin(0, 2);
|
||||
// -5
|
||||
```
|
||||
|
||||
上面代码找出数组中第一个小于0的成员。
|
||||
上面代码找出数组中第一个小于 0 的成员。
|
||||
|
||||
```javascript
|
||||
[1, 5, 10, 15].find(function(value, index, arr) {
|
||||
@ -595,7 +595,7 @@ i32a.copyWithin(0, 2);
|
||||
|
||||
上面代码中,`indexOf`方法无法识别数组的`NaN`成员,但是`findIndex`方法可以借助`Object.is`方法做到。
|
||||
|
||||
## 数组实例的fill()
|
||||
## 数组实例的 fill()
|
||||
|
||||
`fill`方法使用给定值,填充一个数组。
|
||||
|
||||
@ -616,7 +616,7 @@ new Array(3).fill(7)
|
||||
// ['a', 7, 'c']
|
||||
```
|
||||
|
||||
上面代码表示,`fill`方法从1号位开始,向原数组填充7,到2号位之前结束。
|
||||
上面代码表示,`fill`方法从 1 号位开始,向原数组填充 7,到 2 号位之前结束。
|
||||
|
||||
## 数组实例的 entries(),keys() 和 values()
|
||||
|
||||
@ -715,7 +715,7 @@ contains(['foo', 'bar'], 'baz'); // => false
|
||||
Array(3) // [, , ,]
|
||||
```
|
||||
|
||||
上面代码中,`Array(3)`返回一个具有3个空位的数组。
|
||||
上面代码中,`Array(3)`返回一个具有 3 个空位的数组。
|
||||
|
||||
注意,空位不是`undefined`,一个位置的值等于`undefined`,依然是有值的。空位是没有任何值,`in`运算符可以说明这一点。
|
||||
|
||||
@ -724,7 +724,7 @@ Array(3) // [, , ,]
|
||||
0 in [, , ,] // false
|
||||
```
|
||||
|
||||
上面代码说明,第一个数组的0号位置是有值的,第二个数组的0号位置没有值。
|
||||
上面代码说明,第一个数组的 0 号位置是有值的,第二个数组的 0 号位置没有值。
|
||||
|
||||
ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
|
||||
|
||||
@ -816,4 +816,3 @@ for (let i of arr) {
|
||||
```
|
||||
|
||||
由于空位的处理规则非常不统一,所以建议避免出现空位。
|
||||
|
||||
|
@ -1,38 +1,38 @@
|
||||
# ArrayBuffer
|
||||
|
||||
`ArrayBuffer`对象、`TypedArray`视图和`DataView`视图是 JavaScript 操作二进制数据的一个接口。这些对象早就存在,属于独立的规格(2011年2月发布),ES6 将它们纳入了 ECMAScript 规格,并且增加了新的方法。它们都是以数组的语法处理二进制数据,所以统称为二进制数组。
|
||||
`ArrayBuffer`对象、`TypedArray`视图和`DataView`视图是 JavaScript 操作二进制数据的一个接口。这些对象早就存在,属于独立的规格(2011 年 2 月发布),ES6 将它们纳入了 ECMAScript 规格,并且增加了新的方法。它们都是以数组的语法处理二进制数据,所以统称为二进制数组。
|
||||
|
||||
这个接口的原始设计目的,与 WebGL 项目有关。所谓 WebGL,就是指浏览器与显卡之间的通信接口,为了满足 JavaScript 与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个32位整数,两端的 JavaScript 脚本与显卡都要进行格式转化,将非常耗时。这时要是存在一种机制,可以像 C 语言那样,直接操作字节,将4个字节的32位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。
|
||||
这个接口的原始设计目的,与 WebGL 项目有关。所谓 WebGL,就是指浏览器与显卡之间的通信接口,为了满足 JavaScript 与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个 32 位整数,两端的 JavaScript 脚本与显卡都要进行格式转化,将非常耗时。这时要是存在一种机制,可以像 C 语言那样,直接操作字节,将 4 个字节的 32 位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。
|
||||
|
||||
二进制数组就是在这种背景下诞生的。它很像C语言的数组,允许开发者以数组下标的形式,直接操作内存,大大增强了 JavaScript 处理二进制数据的能力,使得开发者有可能通过 JavaScript 与操作系统的原生接口进行二进制通信。
|
||||
二进制数组就是在这种背景下诞生的。它很像 C 语言的数组,允许开发者以数组下标的形式,直接操作内存,大大增强了 JavaScript 处理二进制数据的能力,使得开发者有可能通过 JavaScript 与操作系统的原生接口进行二进制通信。
|
||||
|
||||
二进制数组由三类对象组成。
|
||||
|
||||
**(1)`ArrayBuffer`对象**:代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。
|
||||
|
||||
**(2)`TypedArray`视图**:共包括9种类型的视图,比如`Uint8Array`(无符号8位整数)数组视图, `Int16Array`(16位整数)数组视图, `Float32Array`(32位浮点数)数组视图等等。
|
||||
**(2)`TypedArray`视图**:共包括 9 种类型的视图,比如`Uint8Array`(无符号 8 位整数)数组视图, `Int16Array`(16 位整数)数组视图, `Float32Array`(32 位浮点数)数组视图等等。
|
||||
|
||||
**(3)`DataView`视图**:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号8位整数)、第二、三个字节是 Int16(16位整数)、第四个字节开始是 Float32(32位浮点数)等等,此外还可以自定义字节序。
|
||||
**(3)`DataView`视图**:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。
|
||||
|
||||
简单说,`ArrayBuffer`对象代表原始的二进制数据,TypedArray视图用来读写简单类型的二进制数据,`DataView`视图用来读写复杂类型的二进制数据。
|
||||
简单说,`ArrayBuffer`对象代表原始的二进制数据,TypedArray 视图用来读写简单类型的二进制数据,`DataView`视图用来读写复杂类型的二进制数据。
|
||||
|
||||
TypedArray 视图支持的数据类型一共有9种(`DataView`视图支持除`Uint8C`以外的其他8种)。
|
||||
TypedArray 视图支持的数据类型一共有 9 种(`DataView`视图支持除`Uint8C`以外的其他 8 种)。
|
||||
|
||||
数据类型 | 字节长度 | 含义 | 对应的C语言类型
|
||||
--------|--------|----|---------------
|
||||
Int8|1|8位带符号整数|signed char
|
||||
Uint8|1|8位不带符号整数|unsigned char
|
||||
Uint8C|1|8位不带符号整数(自动过滤溢出)|unsigned char
|
||||
Int16|2|16位带符号整数|short
|
||||
Uint16|2|16位不带符号整数|unsigned short
|
||||
Int32|4|32位带符号整数|int
|
||||
Uint32|4|32位不带符号的整数|unsigned int
|
||||
Float32|4|32位浮点数|float
|
||||
Float64|8|64位浮点数|double
|
||||
| 数据类型 | 字节长度 | 含义 | 对应的 C 语言类型 |
|
||||
| -------- | -------- | -------------------------------- | ----------------- |
|
||||
| Int8 | 1 | 8 位带符号整数 | signed char |
|
||||
| Uint8 | 1 | 8 位不带符号整数 | unsigned char |
|
||||
| Uint8C | 1 | 8 位不带符号整数(自动过滤溢出) | unsigned char |
|
||||
| Int16 | 2 | 16 位带符号整数 | short |
|
||||
| Uint16 | 2 | 16 位不带符号整数 | unsigned short |
|
||||
| Int32 | 4 | 32 位带符号整数 | int |
|
||||
| Uint32 | 4 | 32 位不带符号的整数 | unsigned int |
|
||||
| Float32 | 4 | 32 位浮点数 | float |
|
||||
| Float64 | 8 | 64 位浮点数 | double |
|
||||
|
||||
注意,二进制数组并不是真正的数组,而是类似数组的对象。
|
||||
|
||||
很多浏览器操作的API,用到了二进制数组操作二进制数据,下面是其中的几个。
|
||||
很多浏览器操作的 API,用到了二进制数组操作二进制数据,下面是其中的几个。
|
||||
|
||||
- File API
|
||||
- XMLHttpRequest
|
||||
@ -52,7 +52,7 @@ Float64|8|64位浮点数|double
|
||||
const buf = new ArrayBuffer(32);
|
||||
```
|
||||
|
||||
上面代码生成了一段32字节的内存区域,每个字节的值默认都是0。可以看到,`ArrayBuffer`构造函数的参数是所需要的内存大小(单位字节)。
|
||||
上面代码生成了一段 32 字节的内存区域,每个字节的值默认都是 0。可以看到,`ArrayBuffer`构造函数的参数是所需要的内存大小(单位字节)。
|
||||
|
||||
为了读写这段内容,需要为它指定视图。`DataView`视图的创建,需要提供`ArrayBuffer`对象实例作为参数。
|
||||
|
||||
@ -62,7 +62,7 @@ const dataView = new DataView(buf);
|
||||
dataView.getUint8(0) // 0
|
||||
```
|
||||
|
||||
上面代码对一段32字节的内存,建立`DataView`视图,然后以不带符号的8位整数格式,从头读取8位二进制数据,结果得到0,因为原始内存的`ArrayBuffer`对象,默认所有位都是0。
|
||||
上面代码对一段 32 字节的内存,建立`DataView`视图,然后以不带符号的 8 位整数格式,从头读取 8 位二进制数据,结果得到 0,因为原始内存的`ArrayBuffer`对象,默认所有位都是 0。
|
||||
|
||||
另一种 TypedArray 视图,与`DataView`视图的一个区别是,它不是一个构造函数,而是一组构造函数,代表不同的数据格式。
|
||||
|
||||
@ -77,7 +77,7 @@ x2[0] = 2;
|
||||
x1[0] // 2
|
||||
```
|
||||
|
||||
上面代码对同一段内存,分别建立两种视图:32位带符号整数(`Int32Array`构造函数)和8位不带符号整数(`Uint8Array`构造函数)。由于两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。
|
||||
上面代码对同一段内存,分别建立两种视图:32 位带符号整数(`Int32Array`构造函数)和 8 位不带符号整数(`Uint8Array`构造函数)。由于两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。
|
||||
|
||||
TypedArray 视图的构造函数,除了接受`ArrayBuffer`实例作为参数,还可以接受普通数组作为参数,直接分配内存生成底层的`ArrayBuffer`实例,并同时完成对这段内存的赋值。
|
||||
|
||||
@ -89,7 +89,7 @@ typedArray[0] = 5;
|
||||
typedArray // [5, 1, 2]
|
||||
```
|
||||
|
||||
上面代码使用TypedArray视图的`Uint8Array`构造函数,新建一个不带符号的8位整数视图。可以看到,`Uint8Array`直接使用普通数组作为参数,对底层内存的赋值同时完成。
|
||||
上面代码使用 TypedArray 视图的`Uint8Array`构造函数,新建一个不带符号的 8 位整数视图。可以看到,`Uint8Array`直接使用普通数组作为参数,对底层内存的赋值同时完成。
|
||||
|
||||
### ArrayBuffer.prototype.byteLength
|
||||
|
||||
@ -120,7 +120,7 @@ const buffer = new ArrayBuffer(8);
|
||||
const newBuffer = buffer.slice(0, 3);
|
||||
```
|
||||
|
||||
上面代码拷贝`buffer`对象的前3个字节(从0开始,到第3个字节前面结束),生成一个新的`ArrayBuffer`对象。`slice`方法其实包含两步,第一步是先分配一段新内存,第二步是将原来那个`ArrayBuffer`对象拷贝过去。
|
||||
上面代码拷贝`buffer`对象的前 3 个字节(从 0 开始,到第 3 个字节前面结束),生成一个新的`ArrayBuffer`对象。`slice`方法其实包含两步,第一步是先分配一段新内存,第二步是将原来那个`ArrayBuffer`对象拷贝过去。
|
||||
|
||||
`slice`方法接受两个参数,第一个参数表示拷贝开始的字节序号(含该字节),第二个参数表示拷贝截止的字节序号(不含该字节)。如果省略第二个参数,则默认到原`ArrayBuffer`对象的结尾。
|
||||
|
||||
@ -128,7 +128,7 @@ const newBuffer = buffer.slice(0, 3);
|
||||
|
||||
### ArrayBuffer.isView()
|
||||
|
||||
`ArrayBuffer`有一个静态方法`isView`,返回一个布尔值,表示参数是否为`ArrayBuffer`的视图实例。这个方法大致相当于判断参数,是否为TypedArray实例或`DataView`实例。
|
||||
`ArrayBuffer`有一个静态方法`isView`,返回一个布尔值,表示参数是否为`ArrayBuffer`的视图实例。这个方法大致相当于判断参数,是否为 TypedArray 实例或`DataView`实例。
|
||||
|
||||
```javascript
|
||||
const buffer = new ArrayBuffer(8);
|
||||
@ -138,34 +138,34 @@ const v = new Int32Array(buffer);
|
||||
ArrayBuffer.isView(v) // true
|
||||
```
|
||||
|
||||
## TypedArray视图
|
||||
## TypedArray 视图
|
||||
|
||||
### 概述
|
||||
|
||||
`ArrayBuffer`对象作为内存区域,可以存放多种类型的数据。同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view)。`ArrayBuffer`有两种视图,一种是TypedArray视图,另一种是`DataView`视图。前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。
|
||||
`ArrayBuffer`对象作为内存区域,可以存放多种类型的数据。同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view)。`ArrayBuffer`有两种视图,一种是 TypedArray 视图,另一种是`DataView`视图。前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。
|
||||
|
||||
目前,TypedArray视图一共包括9种类型,每一种视图都是一种构造函数。
|
||||
目前,TypedArray 视图一共包括 9 种类型,每一种视图都是一种构造函数。
|
||||
|
||||
- **`Int8Array`**:8位有符号整数,长度1个字节。
|
||||
- **`Uint8Array`**:8位无符号整数,长度1个字节。
|
||||
- **`Uint8ClampedArray`**:8位无符号整数,长度1个字节,溢出处理不同。
|
||||
- **`Int16Array`**:16位有符号整数,长度2个字节。
|
||||
- **`Uint16Array`**:16位无符号整数,长度2个字节。
|
||||
- **`Int32Array`**:32位有符号整数,长度4个字节。
|
||||
- **`Uint32Array`**:32位无符号整数,长度4个字节。
|
||||
- **`Float32Array`**:32位浮点数,长度4个字节。
|
||||
- **`Float64Array`**:64位浮点数,长度8个字节。
|
||||
- **`Int8Array`**:8 位有符号整数,长度 1 个字节。
|
||||
- **`Uint8Array`**:8 位无符号整数,长度 1 个字节。
|
||||
- **`Uint8ClampedArray`**:8 位无符号整数,长度 1 个字节,溢出处理不同。
|
||||
- **`Int16Array`**:16 位有符号整数,长度 2 个字节。
|
||||
- **`Uint16Array`**:16 位无符号整数,长度 2 个字节。
|
||||
- **`Int32Array`**:32 位有符号整数,长度 4 个字节。
|
||||
- **`Uint32Array`**:32 位无符号整数,长度 4 个字节。
|
||||
- **`Float32Array`**:32 位浮点数,长度 4 个字节。
|
||||
- **`Float64Array`**:64 位浮点数,长度 8 个字节。
|
||||
|
||||
这9个构造函数生成的数组,统称为TypedArray视图。它们很像普通数组,都有`length`属性,都能用方括号运算符(`[]`)获取单个元素,所有数组的方法,在它们上面都能使用。普通数组与TypedArray数组的差异主要在以下方面。
|
||||
这 9 个构造函数生成的数组,统称为 TypedArray 视图。它们很像普通数组,都有`length`属性,都能用方括号运算符(`[]`)获取单个元素,所有数组的方法,在它们上面都能使用。普通数组与 TypedArray 数组的差异主要在以下方面。
|
||||
|
||||
- TypedArray数组的所有成员,都是同一种类型。
|
||||
- TypedArray数组的成员是连续的,不会有空位。
|
||||
- TypedArray数组成员的默认值为0。比如,`new Array(10)`返回一个普通数组,里面没有任何成员,只是10个空位;`new Uint8Array(10)`返回一个TypedArray数组,里面10个成员都是0。
|
||||
- TypedArray数组只是一层视图,本身不储存数据,它的数据都储存在底层的`ArrayBuffer`对象之中,要获取底层对象必须使用`buffer`属性。
|
||||
- TypedArray 数组的所有成员,都是同一种类型。
|
||||
- TypedArray 数组的成员是连续的,不会有空位。
|
||||
- TypedArray 数组成员的默认值为 0。比如,`new Array(10)`返回一个普通数组,里面没有任何成员,只是 10 个空位;`new Uint8Array(10)`返回一个 TypedArray 数组,里面 10 个成员都是 0。
|
||||
- TypedArray 数组只是一层视图,本身不储存数据,它的数据都储存在底层的`ArrayBuffer`对象之中,要获取底层对象必须使用`buffer`属性。
|
||||
|
||||
### 构造函数
|
||||
|
||||
TypedArray数组提供9种构造函数,用来生成相应类型的数组实例。
|
||||
TypedArray 数组提供 9 种构造函数,用来生成相应类型的数组实例。
|
||||
|
||||
构造函数有多种用法。
|
||||
|
||||
@ -187,15 +187,15 @@ const v2 = new Uint8Array(b, 2);
|
||||
const v3 = new Int16Array(b, 2, 2);
|
||||
```
|
||||
|
||||
上面代码在一段长度为8个字节的内存(`b`)之上,生成了三个视图:`v1`、`v2`和`v3`。
|
||||
上面代码在一段长度为 8 个字节的内存(`b`)之上,生成了三个视图:`v1`、`v2`和`v3`。
|
||||
|
||||
视图的构造函数可以接受三个参数:
|
||||
|
||||
- 第一个参数(必需):视图对应的底层`ArrayBuffer`对象。
|
||||
- 第二个参数(可选):视图开始的字节序号,默认从0开始。
|
||||
- 第二个参数(可选):视图开始的字节序号,默认从 0 开始。
|
||||
- 第三个参数(可选):视图包含的数据个数,默认直到本段内存区域结束。
|
||||
|
||||
因此,`v1`、`v2`和`v3`是重叠的:`v1[0]`是一个32位整数,指向字节0~字节3;`v2[0]`是一个8位无符号整数,指向字节2;`v3[0]`是一个16位整数,指向字节2~字节3。只要任何一个视图对内存有所修改,就会在另外两个视图上反应出来。
|
||||
因此,`v1`、`v2`和`v3`是重叠的:`v1[0]`是一个 32 位整数,指向字节 0 ~字节 3;`v2[0]`是一个 8 位无符号整数,指向字节 2;`v3[0]`是一个 16 位整数,指向字节 2 ~字节 3。只要任何一个视图对内存有所修改,就会在另外两个视图上反应出来。
|
||||
|
||||
注意,`byteOffset`必须与所要建立的数据类型一致,否则会报错。
|
||||
|
||||
@ -205,9 +205,9 @@ const i16 = new Int16Array(buffer, 1);
|
||||
// Uncaught RangeError: start offset of Int16Array should be a multiple of 2
|
||||
```
|
||||
|
||||
上面代码中,新生成一个8个字节的`ArrayBuffer`对象,然后在这个对象的第一个字节,建立带符号的16位整数视图,结果报错。因为,带符号的16位整数需要两个字节,所以`byteOffset`参数必须能够被2整除。
|
||||
上面代码中,新生成一个 8 个字节的`ArrayBuffer`对象,然后在这个对象的第一个字节,建立带符号的 16 位整数视图,结果报错。因为,带符号的 16 位整数需要两个字节,所以`byteOffset`参数必须能够被 2 整除。
|
||||
|
||||
如果想从任意字节开始解读`ArrayBuffer`对象,必须使用`DataView`视图,因为TypedArray视图只提供9种固定的解读格式。
|
||||
如果想从任意字节开始解读`ArrayBuffer`对象,必须使用`DataView`视图,因为 TypedArray 视图只提供 9 种固定的解读格式。
|
||||
|
||||
**(2)TypedArray(length)**
|
||||
|
||||
@ -220,11 +220,11 @@ f64a[1] = 20;
|
||||
f64a[2] = f64a[0] + f64a[1];
|
||||
```
|
||||
|
||||
上面代码生成一个8个成员的`Float64Array`数组(共64字节),然后依次对每个成员赋值。这时,视图构造函数的参数就是成员的个数。可以看到,视图数组的赋值操作与普通数组的操作毫无两样。
|
||||
上面代码生成一个 8 个成员的`Float64Array`数组(共 64 字节),然后依次对每个成员赋值。这时,视图构造函数的参数就是成员的个数。可以看到,视图数组的赋值操作与普通数组的操作毫无两样。
|
||||
|
||||
**(3)TypedArray(typedArray)**
|
||||
|
||||
TypedArray数组的构造函数,可以接受另一个TypedArray实例作为参数。
|
||||
TypedArray 数组的构造函数,可以接受另一个 TypedArray 实例作为参数。
|
||||
|
||||
```javascript
|
||||
const typedArray = new Int8Array(new Uint8Array(4));
|
||||
@ -260,17 +260,17 @@ y[0] // 2
|
||||
|
||||
**(4)TypedArray(arrayLikeObject)**
|
||||
|
||||
构造函数的参数也可以是一个普通数组,然后直接生成TypedArray实例。
|
||||
构造函数的参数也可以是一个普通数组,然后直接生成 TypedArray 实例。
|
||||
|
||||
```javascript
|
||||
const typedArray = new Uint8Array([1, 2, 3, 4]);
|
||||
```
|
||||
|
||||
注意,这时TypedArray视图会重新开辟内存,不会在原数组的内存上建立视图。
|
||||
注意,这时 TypedArray 视图会重新开辟内存,不会在原数组的内存上建立视图。
|
||||
|
||||
上面代码从一个普通的数组,生成一个8位无符号整数的TypedArray实例。
|
||||
上面代码从一个普通的数组,生成一个 8 位无符号整数的 TypedArray 实例。
|
||||
|
||||
TypedArray数组也可以转换回普通数组。
|
||||
TypedArray 数组也可以转换回普通数组。
|
||||
|
||||
```javascript
|
||||
const normalArray = [...typedArray];
|
||||
@ -282,7 +282,7 @@ const normalArray = Array.prototype.slice.call(typedArray);
|
||||
|
||||
### 数组方法
|
||||
|
||||
普通数组的操作方法和属性,对TypedArray数组完全适用。
|
||||
普通数组的操作方法和属性,对 TypedArray 数组完全适用。
|
||||
|
||||
- `TypedArray.prototype.copyWithin(target, start[, end = this.length])`
|
||||
- `TypedArray.prototype.entries()`
|
||||
@ -309,7 +309,7 @@ const normalArray = Array.prototype.slice.call(typedArray);
|
||||
|
||||
上面所有方法的用法,请参阅数组方法的介绍,这里不再重复了。
|
||||
|
||||
注意,TypedArray数组没有`concat`方法。如果想要合并多个TypedArray数组,可以用下面这个函数。
|
||||
注意,TypedArray 数组没有`concat`方法。如果想要合并多个 TypedArray 数组,可以用下面这个函数。
|
||||
|
||||
```javascript
|
||||
function concatenate(resultConstructor, ...arrays) {
|
||||
@ -330,7 +330,7 @@ concatenate(Uint8Array, Uint8Array.of(1, 2), Uint8Array.of(3, 4))
|
||||
// Uint8Array [1, 2, 3, 4]
|
||||
```
|
||||
|
||||
另外,TypedArray数组与普通数组一样,部署了Iterator接口,所以可以被遍历。
|
||||
另外,TypedArray 数组与普通数组一样,部署了 Iterator 接口,所以可以被遍历。
|
||||
|
||||
```javascript
|
||||
let ui8 = Uint8Array.of(0, 1, 2);
|
||||
@ -355,9 +355,9 @@ for (let i = 0; i < int32View.length; i++) {
|
||||
}
|
||||
```
|
||||
|
||||
上面代码生成一个16字节的`ArrayBuffer`对象,然后在它的基础上,建立了一个32位整数的视图。由于每个32位整数占据4个字节,所以一共可以写入4个整数,依次为0,2,4,6。
|
||||
上面代码生成一个 16 字节的`ArrayBuffer`对象,然后在它的基础上,建立了一个 32 位整数的视图。由于每个 32 位整数占据 4 个字节,所以一共可以写入 4 个整数,依次为 0,2,4,6。
|
||||
|
||||
如果在这段数据上接着建立一个16位整数的视图,则可以读出完全不一样的结果。
|
||||
如果在这段数据上接着建立一个 16 位整数的视图,则可以读出完全不一样的结果。
|
||||
|
||||
```javascript
|
||||
const int16View = new Int16Array(buffer);
|
||||
@ -375,11 +375,11 @@ for (let i = 0; i < int16View.length; i++) {
|
||||
// Entry 7: 0
|
||||
```
|
||||
|
||||
由于每个16位整数占据2个字节,所以整个`ArrayBuffer`对象现在分成8段。然后,由于x86体系的计算机都采用小端字节序(little endian),相对重要的字节排在后面的内存地址,相对不重要字节排在前面的内存地址,所以就得到了上面的结果。
|
||||
由于每个 16 位整数占据 2 个字节,所以整个`ArrayBuffer`对象现在分成 8 段。然后,由于 x86 体系的计算机都采用小端字节序(little endian),相对重要的字节排在后面的内存地址,相对不重要字节排在前面的内存地址,所以就得到了上面的结果。
|
||||
|
||||
比如,一个占据四个字节的16进制数`0x12345678`,决定其大小的最重要的字节是“12”,最不重要的是“78”。小端字节序将最不重要的字节排在前面,储存顺序就是`78563412`;大端字节序则完全相反,将最重要的字节排在前面,储存顺序就是`12345678`。目前,所有个人电脑几乎都是小端字节序,所以TypedArray数组内部也采用小端字节序读写数据,或者更准确的说,按照本机操作系统设定的字节序读写数据。
|
||||
比如,一个占据四个字节的 16 进制数`0x12345678`,决定其大小的最重要的字节是“12”,最不重要的是“78”。小端字节序将最不重要的字节排在前面,储存顺序就是`78563412`;大端字节序则完全相反,将最重要的字节排在前面,储存顺序就是`12345678`。目前,所有个人电脑几乎都是小端字节序,所以 TypedArray 数组内部也采用小端字节序读写数据,或者更准确的说,按照本机操作系统设定的字节序读写数据。
|
||||
|
||||
这并不意味大端字节序不重要,事实上,很多网络设备和特定的操作系统采用的是大端字节序。这就带来一个严重的问题:如果一段数据是大端字节序,TypedArray数组将无法正确解析,因为它只能处理小端字节序!为了解决这个问题,JavaScript引入`DataView`对象,可以设定字节序,下文会详细介绍。
|
||||
这并不意味大端字节序不重要,事实上,很多网络设备和特定的操作系统采用的是大端字节序。这就带来一个严重的问题:如果一段数据是大端字节序,TypedArray 数组将无法正确解析,因为它只能处理小端字节序!为了解决这个问题,JavaScript 引入`DataView`对象,可以设定字节序,下文会详细介绍。
|
||||
|
||||
下面是另一个例子。
|
||||
|
||||
@ -426,9 +426,9 @@ function getPlatformEndianness() {
|
||||
}
|
||||
```
|
||||
|
||||
总之,与普通数组相比,TypedArray数组的最大优点就是可以直接操作内存,不需要数据类型转换,所以速度快得多。
|
||||
总之,与普通数组相比,TypedArray 数组的最大优点就是可以直接操作内存,不需要数据类型转换,所以速度快得多。
|
||||
|
||||
### BYTES_PER_ELEMENT属性
|
||||
### BYTES_PER_ELEMENT 属性
|
||||
|
||||
每一种视图的构造函数,都有一个`BYTES_PER_ELEMENT`属性,表示这种数据类型占据的字节数。
|
||||
|
||||
@ -443,9 +443,9 @@ Float32Array.BYTES_PER_ELEMENT // 4
|
||||
Float64Array.BYTES_PER_ELEMENT // 8
|
||||
```
|
||||
|
||||
这个属性在TypedArray实例上也能获取,即有`TypedArray.prototype.BYTES_PER_ELEMENT`。
|
||||
这个属性在 TypedArray 实例上也能获取,即有`TypedArray.prototype.BYTES_PER_ELEMENT`。
|
||||
|
||||
### ArrayBuffer与字符串的互相转换
|
||||
### ArrayBuffer 与字符串的互相转换
|
||||
|
||||
`ArrayBuffer`转为字符串,或者字符串转为`ArrayBuffer`,有一个前提,即字符串的编码方法是确定的。假定字符串采用 UTF-16 编码(JavaScript 的内部编码方式),可以自己编写转换函数。
|
||||
|
||||
@ -470,9 +470,9 @@ function str2ab(str) {
|
||||
|
||||
### 溢出
|
||||
|
||||
不同的视图类型,所能容纳的数值范围是确定的。超出这个范围,就会出现溢出。比如,8位视图只能容纳一个8位的二进制值,如果放入一个9位的值,就会溢出。
|
||||
不同的视图类型,所能容纳的数值范围是确定的。超出这个范围,就会出现溢出。比如,8 位视图只能容纳一个 8 位的二进制值,如果放入一个 9 位的值,就会溢出。
|
||||
|
||||
TypedArray数组的溢出处理规则,简单来说,就是抛弃溢出的位,然后按照视图类型进行解释。
|
||||
TypedArray 数组的溢出处理规则,简单来说,就是抛弃溢出的位,然后按照视图类型进行解释。
|
||||
|
||||
```javascript
|
||||
const uint8 = new Uint8Array(1);
|
||||
@ -484,14 +484,14 @@ uint8[0] = -1;
|
||||
uint8[0] // 255
|
||||
```
|
||||
|
||||
上面代码中,`uint8`是一个8位视图,而256的二进制形式是一个9位的值`100000000`,这时就会发生溢出。根据规则,只会保留后8位,即`00000000`。`uint8`视图的解释规则是无符号的8位整数,所以`00000000`就是`0`。
|
||||
上面代码中,`uint8`是一个 8 位视图,而 256 的二进制形式是一个 9 位的值`100000000`,这时就会发生溢出。根据规则,只会保留后 8 位,即`00000000`。`uint8`视图的解释规则是无符号的 8 位整数,所以`00000000`就是`0`。
|
||||
|
||||
负数在计算机内部采用“2的补码”表示,也就是说,将对应的正数值进行否运算,然后加`1`。比如,`-1`对应的正值是`1`,进行否运算以后,得到`11111110`,再加上`1`就是补码形式`11111111`。`uint8`按照无符号的8位整数解释`11111111`,返回结果就是`255`。
|
||||
负数在计算机内部采用“2 的补码”表示,也就是说,将对应的正数值进行否运算,然后加`1`。比如,`-1`对应的正值是`1`,进行否运算以后,得到`11111110`,再加上`1`就是补码形式`11111111`。`uint8`按照无符号的 8 位整数解释`11111111`,返回结果就是`255`。
|
||||
|
||||
一个简单转换规则,可以这样表示。
|
||||
|
||||
- 正向溢出(overflow):当输入值大于当前数据类型的最大值,结果等于当前数据类型的最小值加上余值,再减去1。
|
||||
- 负向溢出(underflow):当输入值小于当前数据类型的最小值,结果等于当前数据类型的最大值减去余值,再加上1。
|
||||
- 正向溢出(overflow):当输入值大于当前数据类型的最大值,结果等于当前数据类型的最小值加上余值,再减去 1。
|
||||
- 负向溢出(underflow):当输入值小于当前数据类型的最小值,结果等于当前数据类型的最大值减去余值,再加上 1。
|
||||
|
||||
上面的“余值”就是模运算的结果,即 JavaScript 里面的`%`运算符的结果。
|
||||
|
||||
@ -500,7 +500,7 @@ uint8[0] // 255
|
||||
12 % 5 // 2
|
||||
```
|
||||
|
||||
上面代码中,12除以4是没有余值的,而除以5会得到余值2。
|
||||
上面代码中,12 除以 4 是没有余值的,而除以 5 会得到余值 2。
|
||||
|
||||
请看下面的例子。
|
||||
|
||||
@ -514,9 +514,9 @@ int8[0] = -129;
|
||||
int8[0] // 127
|
||||
```
|
||||
|
||||
上面例子中,`int8`是一个带符号的8位整数视图,它的最大值是127,最小值是-128。输入值为`128`时,相当于正向溢出`1`,根据“最小值加上余值(128除以127的余值是1),再减去1”的规则,就会返回`-128`;输入值为`-129`时,相当于负向溢出`1`,根据“最大值减去余值(-129除以-128的余值是1),再加上1”的规则,就会返回`127`。
|
||||
上面例子中,`int8`是一个带符号的 8 位整数视图,它的最大值是 127,最小值是-128。输入值为`128`时,相当于正向溢出`1`,根据“最小值加上余值(128 除以 127 的余值是 1),再减去 1”的规则,就会返回`-128`;输入值为`-129`时,相当于负向溢出`1`,根据“最大值减去余值(-129 除以-128 的余值是 1),再加上 1”的规则,就会返回`127`。
|
||||
|
||||
`Uint8ClampedArray`视图的溢出规则,与上面的规则不同。它规定,凡是发生正向溢出,该值一律等于当前数据类型的最大值,即255;如果发生负向溢出,该值一律等于当前数据类型的最小值,即0。
|
||||
`Uint8ClampedArray`视图的溢出规则,与上面的规则不同。它规定,凡是发生正向溢出,该值一律等于当前数据类型的最大值,即 255;如果发生负向溢出,该值一律等于当前数据类型的最小值,即 0。
|
||||
|
||||
```javascript
|
||||
const uint8c = new Uint8ClampedArray(1);
|
||||
@ -528,11 +528,11 @@ uint8c[0] = -1;
|
||||
uint8c[0] // 0
|
||||
```
|
||||
|
||||
上面例子中,`uint8C`是一个`Uint8ClampedArray`视图,正向溢出时都返回255,负向溢出都返回0。
|
||||
上面例子中,`uint8C`是一个`Uint8ClampedArray`视图,正向溢出时都返回 255,负向溢出都返回 0。
|
||||
|
||||
### TypedArray.prototype.buffer
|
||||
|
||||
TypedArray实例的`buffer`属性,返回整段内存区域对应的`ArrayBuffer`对象。该属性为只读属性。
|
||||
TypedArray 实例的`buffer`属性,返回整段内存区域对应的`ArrayBuffer`对象。该属性为只读属性。
|
||||
|
||||
```javascript
|
||||
const a = new Float32Array(64);
|
||||
@ -543,7 +543,7 @@ const b = new Uint8Array(a.buffer);
|
||||
|
||||
### TypedArray.prototype.byteLength,TypedArray.prototype.byteOffset
|
||||
|
||||
`byteLength`属性返回TypedArray数组占据的内存长度,单位为字节。`byteOffset`属性返回TypedArray数组从底层`ArrayBuffer`对象的哪个字节开始。这两个属性都是只读属性。
|
||||
`byteLength`属性返回 TypedArray 数组占据的内存长度,单位为字节。`byteOffset`属性返回 TypedArray 数组从底层`ArrayBuffer`对象的哪个字节开始。这两个属性都是只读属性。
|
||||
|
||||
```javascript
|
||||
const b = new ArrayBuffer(8);
|
||||
@ -563,7 +563,7 @@ v3.byteOffset // 2
|
||||
|
||||
### TypedArray.prototype.length
|
||||
|
||||
`length`属性表示TypedArray数组含有多少个成员。注意将`byteLength`属性和`length`属性区分,前者是字节长度,后者是成员长度。
|
||||
`length`属性表示 TypedArray 数组含有多少个成员。注意将`byteLength`属性和`length`属性区分,前者是字节长度,后者是成员长度。
|
||||
|
||||
```javascript
|
||||
const a = new Int16Array(8);
|
||||
@ -574,7 +574,7 @@ a.byteLength // 16
|
||||
|
||||
### TypedArray.prototype.set()
|
||||
|
||||
TypedArray数组的`set`方法用于复制数组(普通数组或TypedArray数组),也就是将一段内容完全复制到另一段内存。
|
||||
TypedArray 数组的`set`方法用于复制数组(普通数组或 TypedArray 数组),也就是将一段内容完全复制到另一段内存。
|
||||
|
||||
```javascript
|
||||
const a = new Uint8Array(8);
|
||||
@ -598,7 +598,7 @@ b.set(a, 2)
|
||||
|
||||
### TypedArray.prototype.subarray()
|
||||
|
||||
`subarray`方法是对于TypedArray数组的一部分,再建立一个新的视图。
|
||||
`subarray`方法是对于 TypedArray 数组的一部分,再建立一个新的视图。
|
||||
|
||||
```javascript
|
||||
const a = new Uint16Array(8);
|
||||
@ -608,11 +608,11 @@ a.byteLength // 16
|
||||
b.byteLength // 2
|
||||
```
|
||||
|
||||
`subarray`方法的第一个参数是起始的成员序号,第二个参数是结束的成员序号(不含该成员),如果省略则包含剩余的全部成员。所以,上面代码的`a.subarray(2,3)`,意味着b只包含`a[2]`一个成员,字节长度为2。
|
||||
`subarray`方法的第一个参数是起始的成员序号,第二个参数是结束的成员序号(不含该成员),如果省略则包含剩余的全部成员。所以,上面代码的`a.subarray(2,3)`,意味着 b 只包含`a[2]`一个成员,字节长度为 2。
|
||||
|
||||
### TypedArray.prototype.slice()
|
||||
|
||||
TypeArray实例的`slice`方法,可以返回一个指定位置的新的TypedArray实例。
|
||||
TypeArray 实例的`slice`方法,可以返回一个指定位置的新的 TypedArray 实例。
|
||||
|
||||
```javascript
|
||||
let ui8 = Uint8Array.of(0, 1, 2);
|
||||
@ -620,20 +620,20 @@ ui8.slice(-1)
|
||||
// Uint8Array [ 2 ]
|
||||
```
|
||||
|
||||
上面代码中,`ui8`是8位无符号整数数组视图的一个实例。它的`slice`方法可以从当前视图之中,返回一个新的视图实例。
|
||||
上面代码中,`ui8`是 8 位无符号整数数组视图的一个实例。它的`slice`方法可以从当前视图之中,返回一个新的视图实例。
|
||||
|
||||
`slice`方法的参数,表示原数组的具体位置,开始生成新数组。负值表示逆向的位置,即-1为倒数第一个位置,-2表示倒数第二个位置,以此类推。
|
||||
`slice`方法的参数,表示原数组的具体位置,开始生成新数组。负值表示逆向的位置,即-1 为倒数第一个位置,-2 表示倒数第二个位置,以此类推。
|
||||
|
||||
### TypedArray.of()
|
||||
|
||||
TypedArray数组的所有构造函数,都有一个静态方法`of`,用于将参数转为一个TypedArray实例。
|
||||
TypedArray 数组的所有构造函数,都有一个静态方法`of`,用于将参数转为一个 TypedArray 实例。
|
||||
|
||||
```javascript
|
||||
Float32Array.of(0.151, -8, 3.7)
|
||||
// Float32Array [ 0.151, -8, 3.7 ]
|
||||
```
|
||||
|
||||
下面三种方法都会生成同样一个TypedArray数组。
|
||||
下面三种方法都会生成同样一个 TypedArray 数组。
|
||||
|
||||
```javascript
|
||||
// 方法一
|
||||
@ -651,14 +651,14 @@ tarr[2] = 3;
|
||||
|
||||
### TypedArray.from()
|
||||
|
||||
静态方法`from`接受一个可遍历的数据结构(比如数组)作为参数,返回一个基于这个结构的TypedArray实例。
|
||||
静态方法`from`接受一个可遍历的数据结构(比如数组)作为参数,返回一个基于这个结构的 TypedArray 实例。
|
||||
|
||||
```javascript
|
||||
Uint16Array.from([0, 1, 2])
|
||||
// Uint16Array [ 0, 1, 2 ]
|
||||
```
|
||||
|
||||
这个方法还可以将一种TypedArray实例,转为另一种。
|
||||
这个方法还可以将一种 TypedArray 实例,转为另一种。
|
||||
|
||||
```javascript
|
||||
const ui16 = Uint16Array.from(Uint8Array.of(0, 1, 2));
|
||||
@ -675,7 +675,7 @@ Int16Array.from(Int8Array.of(127, 126, 125), x => 2 * x)
|
||||
// Int16Array [ 254, 252, 250 ]
|
||||
```
|
||||
|
||||
上面的例子中,`from`方法没有发生溢出,这说明遍历不是针对原来的8位整数数组。也就是说,`from`会将第一个参数指定的TypedArray数组,拷贝到另一段内存之中,处理之后再将结果转成指定的数组格式。
|
||||
上面的例子中,`from`方法没有发生溢出,这说明遍历不是针对原来的 8 位整数数组。也就是说,`from`会将第一个参数指定的 TypedArray 数组,拷贝到另一段内存之中,处理之后再将结果转成指定的数组格式。
|
||||
|
||||
## 复合视图
|
||||
|
||||
@ -689,13 +689,13 @@ const usernameView = new Uint8Array(buffer, 4, 16);
|
||||
const amountDueView = new Float32Array(buffer, 20, 1);
|
||||
```
|
||||
|
||||
上面代码将一个24字节长度的`ArrayBuffer`对象,分成三个部分:
|
||||
上面代码将一个 24 字节长度的`ArrayBuffer`对象,分成三个部分:
|
||||
|
||||
- 字节0到字节3:1个32位无符号整数
|
||||
- 字节4到字节19:16个8位整数
|
||||
- 字节20到字节23:1个32位浮点数
|
||||
- 字节 0 到字节 3:1 个 32 位无符号整数
|
||||
- 字节 4 到字节 19:16 个 8 位整数
|
||||
- 字节 20 到字节 23:1 个 32 位浮点数
|
||||
|
||||
这种数据结构可以用如下的C语言描述:
|
||||
这种数据结构可以用如下的 C 语言描述:
|
||||
|
||||
```c
|
||||
struct someStruct {
|
||||
@ -705,11 +705,11 @@ struct someStruct {
|
||||
};
|
||||
```
|
||||
|
||||
## DataView视图
|
||||
## DataView 视图
|
||||
|
||||
如果一段数据包括多种类型(比如服务器传来的HTTP数据),这时除了建立`ArrayBuffer`对象的复合视图以外,还可以通过`DataView`视图进行操作。
|
||||
如果一段数据包括多种类型(比如服务器传来的 HTTP 数据),这时除了建立`ArrayBuffer`对象的复合视图以外,还可以通过`DataView`视图进行操作。
|
||||
|
||||
`DataView`视图提供更多操作选项,而且支持设定字节序。本来,在设计目的上,`ArrayBuffer`对象的各种TypedArray视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而`DataView`视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。
|
||||
`DataView`视图提供更多操作选项,而且支持设定字节序。本来,在设计目的上,`ArrayBuffer`对象的各种 TypedArray 视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而`DataView`视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。
|
||||
|
||||
`DataView`视图本身也是构造函数,接受一个`ArrayBuffer`对象作为参数,生成视图。
|
||||
|
||||
@ -724,22 +724,22 @@ const buffer = new ArrayBuffer(24);
|
||||
const dv = new DataView(buffer);
|
||||
```
|
||||
|
||||
`DataView`实例有以下属性,含义与TypedArray实例的同名方法相同。
|
||||
`DataView`实例有以下属性,含义与 TypedArray 实例的同名方法相同。
|
||||
|
||||
- `DataView.prototype.buffer`:返回对应的ArrayBuffer对象
|
||||
- `DataView.prototype.buffer`:返回对应的 ArrayBuffer 对象
|
||||
- `DataView.prototype.byteLength`:返回占据的内存字节长度
|
||||
- `DataView.prototype.byteOffset`:返回当前视图从对应的ArrayBuffer对象的哪个字节开始
|
||||
- `DataView.prototype.byteOffset`:返回当前视图从对应的 ArrayBuffer 对象的哪个字节开始
|
||||
|
||||
`DataView`实例提供8个方法读取内存。
|
||||
`DataView`实例提供 8 个方法读取内存。
|
||||
|
||||
- **`getInt8`**:读取1个字节,返回一个8位整数。
|
||||
- **`getUint8`**:读取1个字节,返回一个无符号的8位整数。
|
||||
- **`getInt16`**:读取2个字节,返回一个16位整数。
|
||||
- **`getUint16`**:读取2个字节,返回一个无符号的16位整数。
|
||||
- **`getInt32`**:读取4个字节,返回一个32位整数。
|
||||
- **`getUint32`**:读取4个字节,返回一个无符号的32位整数。
|
||||
- **`getFloat32`**:读取4个字节,返回一个32位浮点数。
|
||||
- **`getFloat64`**:读取8个字节,返回一个64位浮点数。
|
||||
- **`getInt8`**:读取 1 个字节,返回一个 8 位整数。
|
||||
- **`getUint8`**:读取 1 个字节,返回一个无符号的 8 位整数。
|
||||
- **`getInt16`**:读取 2 个字节,返回一个 16 位整数。
|
||||
- **`getUint16`**:读取 2 个字节,返回一个无符号的 16 位整数。
|
||||
- **`getInt32`**:读取 4 个字节,返回一个 32 位整数。
|
||||
- **`getUint32`**:读取 4 个字节,返回一个无符号的 32 位整数。
|
||||
- **`getFloat32`**:读取 4 个字节,返回一个 32 位浮点数。
|
||||
- **`getFloat64`**:读取 8 个字节,返回一个 64 位浮点数。
|
||||
|
||||
这一系列`get`方法的参数都是一个字节序号(不能是负数,否则会报错),表示从哪个字节开始读取。
|
||||
|
||||
@ -757,7 +757,7 @@ const v2 = dv.getUint16(1);
|
||||
const v3 = dv.getUint16(3);
|
||||
```
|
||||
|
||||
上面代码读取了`ArrayBuffer`对象的前5个字节,其中有一个8位整数和两个十六位整数。
|
||||
上面代码读取了`ArrayBuffer`对象的前 5 个字节,其中有一个 8 位整数和两个十六位整数。
|
||||
|
||||
如果一次读取两个或两个以上字节,就必须明确数据的存储方式,到底是小端字节序还是大端字节序。默认情况下,`DataView`的`get`方法使用大端字节序解读数据,如果需要使用小端字节序解读,必须在`get`方法的第二个参数指定`true`。
|
||||
|
||||
@ -772,16 +772,16 @@ const v2 = dv.getUint16(3, false);
|
||||
const v3 = dv.getUint16(3);
|
||||
```
|
||||
|
||||
DataView视图提供8个方法写入内存。
|
||||
DataView 视图提供 8 个方法写入内存。
|
||||
|
||||
- **`setInt8`**:写入1个字节的8位整数。
|
||||
- **`setUint8`**:写入1个字节的8位无符号整数。
|
||||
- **`setInt16`**:写入2个字节的16位整数。
|
||||
- **`setUint16`**:写入2个字节的16位无符号整数。
|
||||
- **`setInt32`**:写入4个字节的32位整数。
|
||||
- **`setUint32`**:写入4个字节的32位无符号整数。
|
||||
- **`setFloat32`**:写入4个字节的32位浮点数。
|
||||
- **`setFloat64`**:写入8个字节的64位浮点数。
|
||||
- **`setInt8`**:写入 1 个字节的 8 位整数。
|
||||
- **`setUint8`**:写入 1 个字节的 8 位无符号整数。
|
||||
- **`setInt16`**:写入 2 个字节的 16 位整数。
|
||||
- **`setUint16`**:写入 2 个字节的 16 位无符号整数。
|
||||
- **`setInt32`**:写入 4 个字节的 32 位整数。
|
||||
- **`setUint32`**:写入 4 个字节的 32 位无符号整数。
|
||||
- **`setFloat32`**:写入 4 个字节的 32 位浮点数。
|
||||
- **`setFloat64`**:写入 8 个字节的 64 位浮点数。
|
||||
|
||||
这一系列`set`方法,接受两个参数,第一个参数是字节序号,表示从哪个字节开始写入,第二个参数为写入的数据。对于那些写入两个或两个以上字节的方法,需要指定第三个参数,`false`或者`undefined`表示使用大端字节序写入,`true`表示使用小端字节序写入。
|
||||
|
||||
@ -810,11 +810,11 @@ const littleEndian = (function() {
|
||||
|
||||
## 二进制数组的应用
|
||||
|
||||
大量的Web API用到了`ArrayBuffer`对象和它的视图对象。
|
||||
大量的 Web API 用到了`ArrayBuffer`对象和它的视图对象。
|
||||
|
||||
### AJAX
|
||||
|
||||
传统上,服务器通过AJAX操作只能返回文本数据,即`responseType`属性默认为`text`。`XMLHttpRequest`第二版`XHR2`允许服务器返回二进制数据,这时分成两种情况。如果明确知道返回的二进制数据类型,可以把返回类型(`responseType`)设为`arraybuffer`;如果不知道,就设为`blob`。
|
||||
传统上,服务器通过 AJAX 操作只能返回文本数据,即`responseType`属性默认为`text`。`XMLHttpRequest`第二版`XHR2`允许服务器返回二进制数据,这时分成两种情况。如果明确知道返回的二进制数据类型,可以把返回类型(`responseType`)设为`arraybuffer`;如果不知道,就设为`blob`。
|
||||
|
||||
```javascript
|
||||
let xhr = new XMLHttpRequest();
|
||||
@ -829,7 +829,7 @@ xhr.onload = function () {
|
||||
xhr.send();
|
||||
```
|
||||
|
||||
如果知道传回来的是32位整数,可以像下面这样处理。
|
||||
如果知道传回来的是 32 位整数,可以像下面这样处理。
|
||||
|
||||
```javascript
|
||||
xhr.onreadystatechange = function () {
|
||||
@ -846,7 +846,7 @@ xhr.onreadystatechange = function () {
|
||||
|
||||
### Canvas
|
||||
|
||||
网页`Canvas`元素输出的二进制像素数据,就是TypedArray数组。
|
||||
网页`Canvas`元素输出的二进制像素数据,就是 TypedArray 数组。
|
||||
|
||||
```javascript
|
||||
const canvas = document.getElementById('myCanvas');
|
||||
@ -856,21 +856,21 @@ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const uint8ClampedArray = imageData.data;
|
||||
```
|
||||
|
||||
需要注意的是,上面代码的`uint8ClampedArray`虽然是一个TypedArray数组,但是它的视图类型是一种针对`Canvas`元素的专有类型`Uint8ClampedArray`。这个视图类型的特点,就是专门针对颜色,把每个字节解读为无符号的8位整数,即只能取值0~255,而且发生运算的时候自动过滤高位溢出。这为图像处理带来了巨大的方便。
|
||||
需要注意的是,上面代码的`uint8ClampedArray`虽然是一个 TypedArray 数组,但是它的视图类型是一种针对`Canvas`元素的专有类型`Uint8ClampedArray`。这个视图类型的特点,就是专门针对颜色,把每个字节解读为无符号的 8 位整数,即只能取值 0 ~ 255,而且发生运算的时候自动过滤高位溢出。这为图像处理带来了巨大的方便。
|
||||
|
||||
举例来说,如果把像素的颜色值设为`Uint8Array`类型,那么乘以一个gamma值的时候,就必须这样计算:
|
||||
举例来说,如果把像素的颜色值设为`Uint8Array`类型,那么乘以一个 gamma 值的时候,就必须这样计算:
|
||||
|
||||
```javascript
|
||||
u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));
|
||||
```
|
||||
|
||||
因为`Uint8Array`类型对于大于255的运算结果(比如`0xFF+1`),会自动变为`0x00`,所以图像处理必须要像上面这样算。这样做很麻烦,而且影响性能。如果将颜色值设为`Uint8ClampedArray`类型,计算就简化许多。
|
||||
因为`Uint8Array`类型对于大于 255 的运算结果(比如`0xFF+1`),会自动变为`0x00`,所以图像处理必须要像上面这样算。这样做很麻烦,而且影响性能。如果将颜色值设为`Uint8ClampedArray`类型,计算就简化许多。
|
||||
|
||||
```javascript
|
||||
pixels[i] *= gamma;
|
||||
```
|
||||
|
||||
`Uint8ClampedArray`类型确保将小于0的值设为0,将大于255的值设为255。注意,IE 10不支持该类型。
|
||||
`Uint8ClampedArray`类型确保将小于 0 的值设为 0,将大于 255 的值设为 255。注意,IE 10 不支持该类型。
|
||||
|
||||
### WebSocket
|
||||
|
||||
@ -896,7 +896,7 @@ socket.addEventListener('message', function (event) {
|
||||
|
||||
### Fetch API
|
||||
|
||||
Fetch API取回的数据,就是`ArrayBuffer`对象。
|
||||
Fetch API 取回的数据,就是`ArrayBuffer`对象。
|
||||
|
||||
```javascript
|
||||
fetch(url)
|
||||
@ -923,7 +923,7 @@ reader.onload = function () {
|
||||
};
|
||||
```
|
||||
|
||||
下面以处理bmp文件为例。假定`file`变量是一个指向bmp文件的文件对象,首先读取文件。
|
||||
下面以处理 bmp 文件为例。假定`file`变量是一个指向 bmp 文件的文件对象,首先读取文件。
|
||||
|
||||
```javascript
|
||||
const reader = new FileReader();
|
||||
@ -942,7 +942,7 @@ function processimage(e) {
|
||||
}
|
||||
```
|
||||
|
||||
具体处理图像数据时,先处理bmp的文件头。具体每个文件头的格式和定义,请参阅有关资料。
|
||||
具体处理图像数据时,先处理 bmp 的文件头。具体每个文件头的格式和定义,请参阅有关资料。
|
||||
|
||||
```javascript
|
||||
bitmap.fileheader = {};
|
||||
@ -1097,7 +1097,7 @@ console.log(ia[42]);
|
||||
// 191
|
||||
```
|
||||
|
||||
上面代码中,主线程的原始顺序是先对42号位置赋值,再对37号位置赋值。但是,编译器和 CPU 为了优化,可能会改变这两个操作的执行顺序(因为它们之间互不依赖),先对37号位置赋值,再对42号位置赋值。而执行到一半的时候,Worker 线程可能就会来读取数据,导致打印出`123456`和`191`。
|
||||
上面代码中,主线程的原始顺序是先对 42 号位置赋值,再对 37 号位置赋值。但是,编译器和 CPU 为了优化,可能会改变这两个操作的执行顺序(因为它们之间互不依赖),先对 37 号位置赋值,再对 42 号位置赋值。而执行到一半的时候,Worker 线程可能就会来读取数据,导致打印出`123456`和`191`。
|
||||
|
||||
下面是另一个例子。
|
||||
|
||||
@ -1145,7 +1145,7 @@ console.log(ia[37]); // 123456
|
||||
console.log(ia[42]); // 314159
|
||||
```
|
||||
|
||||
上面代码中,主线程的`Atomics.store`向42号位置的赋值,一定是早于37位置的赋值。只要37号位置等于163,Worker 线程就不会终止循环,而对37号位置和42号位置的取值,一定是在`Atomics.load`操作之后。
|
||||
上面代码中,主线程的`Atomics.store`向 42 号位置的赋值,一定是早于 37 位置的赋值。只要 37 号位置等于 163,Worker 线程就不会终止循环,而对 37 号位置和 42 号位置的取值,一定是在`Atomics.load`操作之后。
|
||||
|
||||
**(2)Atomics.wait(),Atomics.wake()**
|
||||
|
||||
@ -1176,7 +1176,7 @@ Atomics.wait(ia, 37, 163);
|
||||
console.log(ia[37]); // 123456
|
||||
```
|
||||
|
||||
上面代码中,共享内存视图`ia`的第37号位置,原来的值是`163`。进程二使用`Atomics.wait()`方法,指定只要`ia[37]`等于`163`,就进入休眠状态。进程一使用`Atomics.store()`方法,将`123456`放入`ia[37]`,然后使用`Atomics.wake()`方法将监视`ia[37]`的休眠线程唤醒。
|
||||
上面代码中,共享内存视图`ia`的第 37 号位置,原来的值是`163`。进程二使用`Atomics.wait()`方法,指定只要`ia[37]`等于`163`,就进入休眠状态。进程一使用`Atomics.store()`方法,将`123456`放入`ia[37]`,然后使用`Atomics.wake()`方法将监视`ia[37]`的休眠线程唤醒。
|
||||
|
||||
另外,基于`wait`和`wake`这两个方法的锁内存实现,可以看 Lars T Hansen 的 [js-lock-and-condition](https://github.com/lars-t-hansen/js-lock-and-condition) 这个库。
|
||||
|
||||
@ -1225,4 +1225,3 @@ Atomics.xor(sharedArray, index, value)
|
||||
- `Atomics.isLockFree(size)`:返回一个布尔值,表示`Atomics`对象是否可以处理某个`size`的内存锁定。如果返回`false`,应用程序就需要自己来实现锁定。
|
||||
|
||||
`Atomics.compareExchange`的一个用途是,从 SharedArrayBuffer 读取一个值,然后对该值进行某个操作,操作结束以后,检查一下 SharedArrayBuffer 里面原来那个值是否发生变化(即被其他线程改写过)。如果没有改写过,就将它写回原来的位置,否则读取新的值,再重头进行一次操作。
|
||||
|
||||
|
@ -59,7 +59,7 @@ asyncReadFile();
|
||||
|
||||
(3)更广的适用性。
|
||||
|
||||
`co`模块约定,`yield`命令后面只能是 Thunk 函数或 Promise 对象,而`async`函数的`await`命令后面,可以是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
|
||||
`co`模块约定,`yield`命令后面只能是 Thunk 函数或 Promise 对象,而`async`函数的`await`命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
|
||||
|
||||
(4)返回值是 Promise。
|
||||
|
||||
@ -104,7 +104,7 @@ async function asyncPrint(value, ms) {
|
||||
asyncPrint('hello world', 50);
|
||||
```
|
||||
|
||||
上面代码指定50毫秒以后,输出`hello world`。
|
||||
上面代码指定 50 毫秒以后,输出`hello world`。
|
||||
|
||||
由于`async`函数返回的是 Promise 对象,可以作为`await`命令的参数。所以,上面的例子也可以写成下面的形式。
|
||||
|
||||
@ -600,7 +600,7 @@ async function chainAnimationsAsync(elem, animations) {
|
||||
}
|
||||
```
|
||||
|
||||
可以看到Async函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将Generator写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用Generator写法,自动执行器需要用户自己提供。
|
||||
可以看到 Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用 Generator 写法,自动执行器需要用户自己提供。
|
||||
|
||||
## 实例:按顺序完成异步操作
|
||||
|
||||
@ -636,7 +636,7 @@ async function logInOrder(urls) {
|
||||
}
|
||||
```
|
||||
|
||||
上面代码确实大大简化,问题是所有远程操作都是继发。只有前一个URL返回结果,才会去读取下一个URL,这样做效率很差,非常浪费时间。我们需要的是并发发出远程请求。
|
||||
上面代码确实大大简化,问题是所有远程操作都是继发。只有前一个 URL 返回结果,才会去读取下一个 URL,这样做效率很差,非常浪费时间。我们需要的是并发发出远程请求。
|
||||
|
||||
```javascript
|
||||
async function logInOrder(urls) {
|
||||
@ -719,7 +719,7 @@ async function f() {
|
||||
|
||||
上面代码中,`next`方法用`await`处理以后,就不必使用`then`方法了。整个流程已经很接近同步处理了。
|
||||
|
||||
注意,异步遍历器的`next`方法是可以连续调用的,不必等到上一步产生的Promise对象`resolve`以后再调用。这种情况下,`next`方法会累积起来,自动按照每一步的顺序运行下去。下面是一个例子,把所有的`next`方法放在`Promise.all`方法里面。
|
||||
注意,异步遍历器的`next`方法是可以连续调用的,不必等到上一步产生的 Promise 对象`resolve`以后再调用。这种情况下,`next`方法会累积起来,自动按照每一步的顺序运行下去。下面是一个例子,把所有的`next`方法放在`Promise.all`方法里面。
|
||||
|
||||
```javascript
|
||||
const asyncGenObj = createAsyncIterable(['a', 'b']);
|
||||
@ -753,7 +753,7 @@ async function f() {
|
||||
// b
|
||||
```
|
||||
|
||||
上面代码中,`createAsyncIterable()`返回一个异步遍历器,`for...of`循环自动调用这个遍历器的`next`方法,会得到一个Promise对象。`await`用来处理这个Promise对象,一旦`resolve`,就把得到的值(`x`)传入`for...of`的循环体。
|
||||
上面代码中,`createAsyncIterable()`返回一个异步遍历器,`for...of`循环自动调用这个遍历器的`next`方法,会得到一个 Promise 对象。`await`用来处理这个 Promise 对象,一旦`resolve`,就把得到的值(`x`)传入`for...of`的循环体。
|
||||
|
||||
`for await...of`循环的一个用途,是部署了 asyncIterable 操作的异步接口,可以直接放入这个循环。
|
||||
|
||||
@ -988,7 +988,7 @@ async function* createAsyncIterable(syncIterable) {
|
||||
|
||||
上面代码中,由于没有异步操作,所以也就没有使用`await`关键字。
|
||||
|
||||
### yield* 语句
|
||||
### yield\* 语句
|
||||
|
||||
`yield*`语句也可以跟一个异步遍历器。
|
||||
|
||||
@ -1018,4 +1018,3 @@ async function* gen2() {
|
||||
// a
|
||||
// b
|
||||
```
|
||||
|
||||
|
@ -536,7 +536,7 @@ MyArray.prototype = Object.create(Array.prototype, {
|
||||
});
|
||||
```
|
||||
|
||||
上面代码定义了一个继承Array的`MyArray`类。但是,这个类的行为与`Array`完全不一致。
|
||||
上面代码定义了一个继承 Array 的`MyArray`类。但是,这个类的行为与`Array`完全不一致。
|
||||
|
||||
```javascript
|
||||
var colors = new MyArray();
|
||||
@ -712,4 +712,3 @@ class DistributedEdit extends mix(Loggable, Serializable) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -290,7 +290,7 @@ p1.__proto__ === p2.__proto__
|
||||
|
||||
这也意味着,可以通过实例的`__proto__`属性为“类”添加方法。
|
||||
|
||||
> `__proto__` 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的JS引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 `Object.getPrototypeOf` 方法来获取实例对象的原型,然后再来为原型添加方法/属性。
|
||||
> `__proto__` 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 `Object.getPrototypeOf` 方法来获取实例对象的原型,然后再来为原型添加方法/属性。
|
||||
|
||||
```javascript
|
||||
var p1 = new Point(2,3);
|
||||
@ -930,4 +930,3 @@ var y = new Rectangle(3, 4); // 正确
|
||||
上面代码中,`Shape`类不能被实例化,只能用于继承。
|
||||
|
||||
注意,在函数外部,使用`new.target`会报错。
|
||||
|
||||
|
@ -285,7 +285,7 @@ function foo() {
|
||||
}
|
||||
```
|
||||
|
||||
上面的代码,意图是执行后`counter`等于1,但是实际上结果是`counter`等于0。因为函数提升,使得实际执行的代码是下面这样。
|
||||
上面的代码,意图是执行后`counter`等于 1,但是实际上结果是`counter`等于 0。因为函数提升,使得实际执行的代码是下面这样。
|
||||
|
||||
```javascript
|
||||
@add
|
||||
@ -792,4 +792,3 @@ babel.transform("code", {plugins: ["transform-decorators"]})
|
||||
```
|
||||
|
||||
Babel 的官方网站提供一个[在线转码器](https://babeljs.io/repl/),只要勾选 Experimental,就能支持 Decorator 的在线转码。
|
||||
|
||||
|
@ -506,7 +506,7 @@ let {(x): c} = {};
|
||||
let { o: ({ p: p }) } = { o: { p: 2 } };
|
||||
```
|
||||
|
||||
上面6个语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。
|
||||
上面 6 个语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。
|
||||
|
||||
(2)函数参数
|
||||
|
||||
@ -600,9 +600,9 @@ function f({x, y, z}) { ... }
|
||||
f({z: 3, y: 2, x: 1});
|
||||
```
|
||||
|
||||
**(4)提取JSON数据**
|
||||
**(4)提取 JSON 数据**
|
||||
|
||||
解构赋值对提取JSON对象中的数据,尤其有用。
|
||||
解构赋值对提取 JSON 对象中的数据,尤其有用。
|
||||
|
||||
```javascript
|
||||
let jsonData = {
|
||||
@ -637,9 +637,9 @@ jQuery.ajax = function (url, {
|
||||
|
||||
指定参数的默认值,就避免了在函数体内部再写`var foo = config.foo || 'default foo';`这样的语句。
|
||||
|
||||
**(6)遍历Map结构**
|
||||
**(6)遍历 Map 结构**
|
||||
|
||||
任何部署了Iterator接口的对象,都可以用`for...of`循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。
|
||||
任何部署了 Iterator 接口的对象,都可以用`for...of`循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
|
||||
|
||||
```javascript
|
||||
const map = new Map();
|
||||
|
@ -1,8 +1,8 @@
|
||||
# 函数式编程
|
||||
|
||||
JavaScript语言从一诞生,就具有函数式编程的烙印。它将函数作为一种独立的数据类型,与其他数据类型处于完全平等的地位。在JavaScript语言中,你可以采用面向对象编程,也可以采用函数式编程。有人甚至说,JavaScript是有史以来第一种被大规模采用的函数式编程语言。
|
||||
JavaScript 语言从一诞生,就具有函数式编程的烙印。它将函数作为一种独立的数据类型,与其他数据类型处于完全平等的地位。在 JavaScript 语言中,你可以采用面向对象编程,也可以采用函数式编程。有人甚至说,JavaScript 是有史以来第一种被大规模采用的函数式编程语言。
|
||||
|
||||
ES6的种种新增功能,使得函数式编程变得更方便、更强大。本章介绍ES6如何进行函数式编程。
|
||||
ES6 的种种新增功能,使得函数式编程变得更方便、更强大。本章介绍 ES6 如何进行函数式编程。
|
||||
|
||||
## 柯里化
|
||||
|
||||
@ -66,7 +66,7 @@ var flip = f.flip(three);
|
||||
flip(1, 2, 3); // => [2, 1, 3]
|
||||
```
|
||||
|
||||
上面代码中,如果按照正常的参数顺序,10除以5等于2。但是,参数倒置以后得到的新函数,结果就是5除以10,结果得到0.5。如果原函数有3个参数,则只颠倒前两个参数的位置。
|
||||
上面代码中,如果按照正常的参数顺序,10 除以 5 等于 2。但是,参数倒置以后得到的新函数,结果就是 5 除以 10,结果得到 0.5。如果原函数有 3 个参数,则只颠倒前两个参数的位置。
|
||||
|
||||
参数倒置的代码非常简单。
|
||||
|
||||
@ -94,7 +94,7 @@ until = f.until(condition, inc);
|
||||
until(3) // 5
|
||||
```
|
||||
|
||||
上面代码中,第一段的条件是执行到`x`大于100为止,所以`x`初值为0时,会一直执行到101。第二段的条件是执行到等于5为止,所以`x`最后的值是5。
|
||||
上面代码中,第一段的条件是执行到`x`大于 100 为止,所以`x`初值为 0 时,会一直执行到 101。第二段的条件是执行到等于 5 为止,所以`x`最后的值是 5。
|
||||
|
||||
执行边界的实现如下。
|
||||
|
||||
|
@ -237,7 +237,7 @@ foo(undefined, null)
|
||||
(function (a, b, c = 5) {}).length // 2
|
||||
```
|
||||
|
||||
上面代码中,`length`属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。比如,上面最后一个函数,定义了3个参数,其中有一个参数`c`指定了默认值,因此`length`属性等于`3`减去`1`,最后得到`2`。
|
||||
上面代码中,`length`属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。比如,上面最后一个函数,定义了 3 个参数,其中有一个参数`c`指定了默认值,因此`length`属性等于`3`减去`1`,最后得到`2`。
|
||||
|
||||
这是因为`length`属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,后文的 rest 参数也不会计入`length`属性。
|
||||
|
||||
@ -577,7 +577,7 @@ bar.name // "baz"
|
||||
(new Function).name // "anonymous"
|
||||
```
|
||||
|
||||
`bind`返回的函数,`name`属性值会加上`bound `前缀。
|
||||
`bind`返回的函数,`name`属性值会加上`bound`前缀。
|
||||
|
||||
```javascript
|
||||
function foo() {};
|
||||
@ -725,7 +725,7 @@ foo.call({ id: 42 });
|
||||
// id: 42
|
||||
```
|
||||
|
||||
上面代码中,`setTimeout`的参数是一个箭头函数,这个箭头函数的定义生效是在`foo`函数生成时,而它的真正执行要等到100毫秒后。如果是普通函数,执行时`this`应该指向全局对象`window`,这时应该输出`21`。但是,箭头函数导致`this`总是指向函数定义生效时所在的对象(本例是`{id: 42}`),所以输出的是`42`。
|
||||
上面代码中,`setTimeout`的参数是一个箭头函数,这个箭头函数的定义生效是在`foo`函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时`this`应该指向全局对象`window`,这时应该输出`21`。但是,箭头函数导致`this`总是指向函数定义生效时所在的对象(本例是`{id: 42}`),所以输出的是`42`。
|
||||
|
||||
箭头函数可以让`setTimeout`里面的`this`,绑定定义时所在的作用域,而不是指向运行时所在的作用域。下面是另一个例子。
|
||||
|
||||
@ -749,7 +749,7 @@ setTimeout(() => console.log('s2: ', timer.s2), 3100);
|
||||
// s2: 0
|
||||
```
|
||||
|
||||
上面代码中,`Timer`函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的`this`绑定定义时所在的作用域(即`Timer`函数),后者的`this`指向运行时所在的作用域(即全局对象)。所以,3100毫秒之后,`timer.s1`被更新了3次,而`timer.s2`一次都没更新。
|
||||
上面代码中,`Timer`函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的`this`绑定定义时所在的作用域(即`Timer`函数),后者的`this`指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,`timer.s1`被更新了 3 次,而`timer.s2`一次都没更新。
|
||||
|
||||
箭头函数可以让`this`指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。
|
||||
|
||||
@ -792,7 +792,7 @@ function foo() {
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,转换后的ES5版本清楚地说明了,箭头函数里面根本没有自己的`this`,而是引用外层的`this`。
|
||||
上面代码中,转换后的 ES5 版本清楚地说明了,箭头函数里面根本没有自己的`this`,而是引用外层的`this`。
|
||||
|
||||
请问下面的代码之中有几个`this`?
|
||||
|
||||
@ -898,7 +898,7 @@ mult2(plus1(5))
|
||||
// 12
|
||||
```
|
||||
|
||||
箭头函数还有一个功能,就是可以很方便地改写λ演算。
|
||||
箭头函数还有一个功能,就是可以很方便地改写 λ 演算。
|
||||
|
||||
```javascript
|
||||
// λ演算的写法
|
||||
@ -909,7 +909,7 @@ var fix = f => (x => f(v => x(x)(v)))
|
||||
(x => f(v => x(x)(v)));
|
||||
```
|
||||
|
||||
上面两种写法,几乎是一一对应的。由于λ演算对于计算机科学非常重要,这使得我们可以用ES6作为替代工具,探索计算机科学。
|
||||
上面两种写法,几乎是一一对应的。由于 λ 演算对于计算机科学非常重要,这使得我们可以用 ES6 作为替代工具,探索计算机科学。
|
||||
|
||||
## 双冒号运算符
|
||||
|
||||
@ -1178,7 +1178,7 @@ factorial(5) // 120
|
||||
|
||||
上面代码中,参数`total`有默认值`1`,所以调用时不用提供这个值。
|
||||
|
||||
总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言(比如Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。
|
||||
总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言(比如 Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。
|
||||
|
||||
### 严格模式
|
||||
|
||||
@ -1221,7 +1221,7 @@ sum(1, 100000)
|
||||
// Uncaught RangeError: Maximum call stack size exceeded(…)
|
||||
```
|
||||
|
||||
上面代码中,`sum`是一个递归函数,参数`x`是需要累加的值,参数`y`控制递归次数。一旦指定`sum`递归100000次,就会报错,提示超出调用栈的最大次数。
|
||||
上面代码中,`sum`是一个递归函数,参数`x`是需要累加的值,参数`y`控制递归次数。一旦指定`sum`递归 100000 次,就会报错,提示超出调用栈的最大次数。
|
||||
|
||||
蹦床函数(trampoline)可以将递归执行转为循环执行。
|
||||
|
||||
@ -1365,4 +1365,3 @@ try {
|
||||
```
|
||||
|
||||
上面代码中,`JSON.parse`报错只有一种可能:解析失败。因此,可以不需要抛出的错误实例。
|
||||
|
||||
|
@ -138,7 +138,7 @@ g.next() // { value: undefined, done: true }
|
||||
|
||||
Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。
|
||||
|
||||
`next`返回值的value属性,是 Generator 函数向外输出数据;`next`方法还可以接受参数,向 Generator 函数体内输入数据。
|
||||
`next`返回值的 value 属性,是 Generator 函数向外输出数据;`next`方法还可以接受参数,向 Generator 函数体内输入数据。
|
||||
|
||||
```javascript
|
||||
function* gen(x){
|
||||
@ -212,7 +212,7 @@ Thunk 函数是自动执行 Generator 函数的一种方法。
|
||||
|
||||
### 参数的求值策略
|
||||
|
||||
Thunk 函数早在上个世纪60年代就诞生了。
|
||||
Thunk 函数早在上个世纪 60 年代就诞生了。
|
||||
|
||||
那时,编程语言刚刚起步,计算机学家还在研究,编译器怎么写比较好。一个争论的焦点是"求值策略",即函数的参数到底应该何时求值。
|
||||
|
||||
@ -228,7 +228,7 @@ f(x + 5)
|
||||
|
||||
上面代码先定义函数`f`,然后向它传入表达式`x + 5`。请问,这个表达式应该何时求值?
|
||||
|
||||
一种意见是"传值调用"(call by value),即在进入函数体之前,就计算`x + 5`的值(等于6),再将这个值传入函数`f`。C语言就采用这种策略。
|
||||
一种意见是"传值调用"(call by value),即在进入函数体之前,就计算`x + 5`的值(等于 6),再将这个值传入函数`f`。C 语言就采用这种策略。
|
||||
|
||||
```javascript
|
||||
f(x + 5)
|
||||
@ -280,7 +280,7 @@ function f(thunk) {
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,函数f的参数`x + 5`被一个函数替换了。凡是用到原参数的地方,对`Thunk`函数求值即可。
|
||||
上面代码中,函数 f 的参数`x + 5`被一个函数替换了。凡是用到原参数的地方,对`Thunk`函数求值即可。
|
||||
|
||||
这就是 Thunk 函数的定义,它是“传名调用”的一种实现策略,用来替换某个表达式。
|
||||
|
||||
@ -523,7 +523,7 @@ Thunk 函数并不是 Generator 函数自动执行的唯一方案。因为自动
|
||||
|
||||
### 基本用法
|
||||
|
||||
[co 模块](https://github.com/tj/co)是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。
|
||||
[co 模块](https://github.com/tj/co)是著名程序员 TJ Holowaychuk 于 2013 年 6 月发布的一个小工具,用于 Generator 函数的自动执行。
|
||||
|
||||
下面是一个 Generator 函数,用于依次读取两个文件。
|
||||
|
||||
@ -788,4 +788,3 @@ co(function*() {
|
||||
```
|
||||
|
||||
上面代码采用 Stream 模式读取《悲惨世界》的文本文件,对于每个数据块都使用`stream.once`方法,在`data`、`end`、`error`三个事件上添加一次性回调函数。变量`res`只有在`data`事件发生时才有值,然后累加每个数据块之中`valjean`这个词出现的次数。
|
||||
|
||||
|
@ -259,7 +259,7 @@ b.next(12) // { value:8, done:false }
|
||||
b.next(13) // { value:42, done:true }
|
||||
```
|
||||
|
||||
上面代码中,第二次运行`next`方法的时候不带参数,导致y的值等于`2 * undefined`(即`NaN`),除以3以后还是`NaN`,因此返回对象的`value`属性也等于`NaN`。第三次运行`Next`方法的时候不带参数,所以`z`等于`undefined`,返回对象的`value`属性等于`5 + NaN + undefined`,即`NaN`。
|
||||
上面代码中,第二次运行`next`方法的时候不带参数,导致 y 的值等于`2 * undefined`(即`NaN`),除以 3 以后还是`NaN`,因此返回对象的`value`属性也等于`NaN`。第三次运行`Next`方法的时候不带参数,所以`z`等于`undefined`,返回对象的`value`属性等于`5 + NaN + undefined`,即`NaN`。
|
||||
|
||||
如果向`next`方法提供参数,返回结果就完全不一样了。上面代码第一次调用`b`的`next`方法时,返回`x+1`的值`6`;第二次调用`next`方法,将上一次`yield`表达式的值设为`12`,因此`y`等于`24`,返回`y / 3`的值`8`;第三次调用`next`方法,将上一次`yield`表达式的值设为`13`,因此`z`等于`13`,这时`x`等于`5`,`y`等于`24`,所以`return`语句的值等于`42`。
|
||||
|
||||
@ -328,7 +328,7 @@ for (let v of foo()) {
|
||||
// 1 2 3 4 5
|
||||
```
|
||||
|
||||
上面代码使用`for...of`循环,依次显示5个`yield`表达式的值。这里需要注意,一旦`next`方法的返回对象的`done`属性为`true`,`for...of`循环就会中止,且不包含该返回对象,所以上面代码的`return`语句返回的`6`,不包括在`for...of`循环之中。
|
||||
上面代码使用`for...of`循环,依次显示 5 个`yield`表达式的值。这里需要注意,一旦`next`方法的返回对象的`done`属性为`true`,`for...of`循环就会中止,且不包含该返回对象,所以上面代码的`return`语句返回的`6`,不包括在`for...of`循环之中。
|
||||
|
||||
下面是一个利用 Generator 函数和`for...of`循环,实现斐波那契数列的例子。
|
||||
|
||||
@ -599,7 +599,7 @@ try {
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,第二个`next`方法向函数体内传入一个参数42,数值是没有`toUpperCase`方法的,所以会抛出一个 TypeError 错误,被函数体外的`catch`捕获。
|
||||
上面代码中,第二个`next`方法向函数体内传入一个参数 42,数值是没有`toUpperCase`方法的,所以会抛出一个 TypeError 错误,被函数体外的`catch`捕获。
|
||||
|
||||
一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用`next`方法,将返回一个`value`属性等于`undefined`、`done`属性等于`true`的对象,即 JavaScript 引擎认为这个 Generator 已经运行结束了。
|
||||
|
||||
@ -665,7 +665,7 @@ g.return('foo') // { value: "foo", done: true }
|
||||
g.next() // { value: undefined, done: true }
|
||||
```
|
||||
|
||||
上面代码中,遍历器对象`g`调用`return`方法后,返回值的`value`属性就是`return`方法的参数`foo`。并且,Generator函数的遍历就终止了,返回值的`done`属性为`true`,以后再调用`next`方法,`done`属性总是返回`true`。
|
||||
上面代码中,遍历器对象`g`调用`return`方法后,返回值的`value`属性就是`return`方法的参数`foo`。并且,Generator 函数的遍历就终止了,返回值的`done`属性为`true`,以后再调用`next`方法,`done`属性总是返回`true`。
|
||||
|
||||
如果`return`方法调用时,不提供参数,则返回值的`value`属性为`undefined`。
|
||||
|
||||
@ -744,7 +744,7 @@ gen.return(2); // Object {value: 2, done: true}
|
||||
// 替换成 let result = return 2;
|
||||
```
|
||||
|
||||
## yield* 表达式
|
||||
## yield\* 表达式
|
||||
|
||||
如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。
|
||||
|
||||
@ -859,7 +859,7 @@ for(let value of delegatingIterator) {
|
||||
// "Ok, bye."
|
||||
```
|
||||
|
||||
上面代码中,`delegatingIterator`是代理者,`delegatedIterator`是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个Generator函数,有递归的效果。
|
||||
上面代码中,`delegatingIterator`是代理者,`delegatedIterator`是被代理者。由于`yield* delegatedIterator`语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,遍历了多个 Generator 函数,有递归的效果。
|
||||
|
||||
`yield*`后面的 Generator 函数(没有`return`语句时),等同于在 Generator 函数内部,部署一个`for...of`循环。
|
||||
|
||||
@ -881,7 +881,7 @@ function* concat(iter1, iter2) {
|
||||
}
|
||||
```
|
||||
|
||||
上面代码说明,`yield*`后面的Generator函数(没有`return`语句时),不过是`for...of`的一种简写形式,完全可以用后者替代前者。反之,在有`return`语句时,则需要用`var value = yield* iterator`的形式获取`return`语句的值。
|
||||
上面代码说明,`yield*`后面的 Generator 函数(没有`return`语句时),不过是`for...of`的一种简写形式,完全可以用后者替代前者。反之,在有`return`语句时,则需要用`var value = yield* iterator`的形式获取`return`语句的值。
|
||||
|
||||
如果`yield*`后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。
|
||||
|
||||
@ -1027,7 +1027,7 @@ result
|
||||
// ['a', 'b', 'c', 'd', 'e', 'f', 'g']
|
||||
```
|
||||
|
||||
## 作为对象属性的Generator函数
|
||||
## 作为对象属性的 Generator 函数
|
||||
|
||||
如果一个对象的属性是 Generator 函数,可以简写成下面的形式。
|
||||
|
||||
@ -1079,9 +1079,9 @@ let obj = g();
|
||||
obj.a // undefined
|
||||
```
|
||||
|
||||
上面代码中,Generator函数`g`在`this`对象上面添加了一个属性`a`,但是`obj`对象拿不到这个属性。
|
||||
上面代码中,Generator 函数`g`在`this`对象上面添加了一个属性`a`,但是`obj`对象拿不到这个属性。
|
||||
|
||||
Generator函数也不能跟`new`命令一起用,会报错。
|
||||
Generator 函数也不能跟`new`命令一起用,会报错。
|
||||
|
||||
```javascript
|
||||
function* F() {
|
||||
@ -1117,7 +1117,7 @@ obj.b // 2
|
||||
obj.c // 3
|
||||
```
|
||||
|
||||
上面代码中,首先是`F`内部的`this`对象绑定`obj`对象,然后调用它,返回一个 Iterator 对象。这个对象执行三次`next`方法(因为`F`内部有两个`yield`表达式),完成F内部所有代码的运行。这时,所有内部属性都绑定在`obj`对象上了,因此`obj`对象也就成了`F`的实例。
|
||||
上面代码中,首先是`F`内部的`this`对象绑定`obj`对象,然后调用它,返回一个 Iterator 对象。这个对象执行三次`next`方法(因为`F`内部有两个`yield`表达式),完成 F 内部所有代码的运行。这时,所有内部属性都绑定在`obj`对象上了,因此`obj`对象也就成了`F`的实例。
|
||||
|
||||
上面代码中,执行的是遍历器对象`f`,但是生成的对象实例是`obj`,有没有办法将这两个对象统一呢?
|
||||
|
||||
@ -1196,7 +1196,7 @@ var clock = function* () {
|
||||
|
||||
上面的 Generator 实现与 ES5 实现对比,可以看到少了用来保存状态的外部变量`ticking`,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator 之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态。
|
||||
|
||||
### Generator与协程
|
||||
### Generator 与协程
|
||||
|
||||
协程(coroutine)是一种程序运行的方式,可以理解成“协作的线程”或“协作的函数”。协程既可以用单线程实现,也可以用多线程实现。前者是一种特殊的子例程,后者是一种特殊的线程。
|
||||
|
||||
@ -1471,4 +1471,3 @@ function doStuff() {
|
||||
```
|
||||
|
||||
上面的函数,可以用一模一样的`for...of`循环处理!两相一比较,就不难看出 Generator 使得数据或者操作,具备了类似数组的接口。
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
# ECMAScript 6简介
|
||||
# ECMAScript 6 简介
|
||||
|
||||
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
|
||||
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
|
||||
|
||||
## ECMAScript 和 JavaScript 的关系
|
||||
|
||||
一个常见的问题是,ECMAScript 和 JavaScript 到底是什么关系?
|
||||
|
||||
要讲清楚这个问题,需要回顾历史。1996年11月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给国际标准化组织ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布262号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是1.0版。
|
||||
要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给国际标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
|
||||
|
||||
该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。
|
||||
|
||||
@ -16,17 +16,17 @@ ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,
|
||||
|
||||
ECMAScript 2015(简称 ES2015)这个词,也是经常可以看到的。它与 ES6 是什么关系呢?
|
||||
|
||||
2011年,ECMAScript 5.1版发布后,就开始制定6.0版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本。
|
||||
2011 年,ECMAScript 5.1 版发布后,就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本。
|
||||
|
||||
但是,因为这个版本引入的语法功能太多,而且制定过程当中,还有很多组织和个人不断提交新功能。事情很快就变得清楚了,不可能在一个版本里面包括所有将要引入的功能。常规的做法是先发布6.0版,过一段时间再发6.1版,然后是6.2版、6.3版等等。
|
||||
但是,因为这个版本引入的语法功能太多,而且制定过程当中,还有很多组织和个人不断提交新功能。事情很快就变得清楚了,不可能在一个版本里面包括所有将要引入的功能。常规的做法是先发布 6.0 版,过一段时间再发 6.1 版,然后是 6.2 版、6.3 版等等。
|
||||
|
||||
但是,标准的制定者不想这样做。他们想让标准的升级成为常规流程:任何人在任何时候,都可以向标准委员会提交新语法的提案,然后标准委员会每个月开一次会,评估这些提案是否可以接受,需要哪些改进。如果经过多次会议以后,一个提案足够成熟了,就可以正式进入标准了。这就是说,标准的版本升级成为了一个不断滚动的流程,每个月都会有变动。
|
||||
|
||||
标准委员会最终决定,标准在每年的6月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的6月份,草案就自然变成了新一年的版本。这样一来,就不需要以前的版本号了,只要用年份标记就可以了。
|
||||
标准委员会最终决定,标准在每年的 6 月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的 6 月份,草案就自然变成了新一年的版本。这样一来,就不需要以前的版本号了,只要用年份标记就可以了。
|
||||
|
||||
ES6 的第一个版本,就这样在2015年6月发布了,正式名称就是《ECMAScript 2015标准》(简称 ES2015)。2016年6月,小幅修订的《ECMAScript 2016标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的`includes`方法和指数运算符),基本上是同一个标准。根据计划,2017年6月发布 ES2017 标准。
|
||||
ES6 的第一个版本,就这样在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的`includes`方法和指数运算符),基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准。
|
||||
|
||||
因此,ES6 既是一个历史名词,也是一个泛指,含义是5.1版以后的 JavaScript 的下一代标准,涵盖了ES2015、ES2016、ES2017等等,而ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。
|
||||
因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。
|
||||
|
||||
## 语法提案的批准流程
|
||||
|
||||
@ -42,35 +42,35 @@ ES6 的第一个版本,就这样在2015年6月发布了,正式名称就是
|
||||
|
||||
一个提案只要能进入 Stage 2,就差不多肯定会包括在以后的正式标准里面。ECMAScript 当前的所有提案,可以在 TC39 的官方网站[Github.com/tc39/ecma262](https://github.com/tc39/ecma262)查看。
|
||||
|
||||
本书的写作目标之一,是跟踪 ECMAScript 语言的最新进展,介绍5.1版本以后所有的新语法。对于那些明确或很有希望,将要列入标准的新语法,都将予以介绍。
|
||||
本书的写作目标之一,是跟踪 ECMAScript 语言的最新进展,介绍 5.1 版本以后所有的新语法。对于那些明确或很有希望,将要列入标准的新语法,都将予以介绍。
|
||||
|
||||
## ECMAScript 的历史
|
||||
|
||||
ES6 从开始制定到最后发布,整整用了15年。
|
||||
ES6 从开始制定到最后发布,整整用了 15 年。
|
||||
|
||||
前面提到,ECMAScript 1.0 是1997年发布的,接下来的两年,连续发布了 ECMAScript 2.0(1998年6月)和 ECMAScript 3.0(1999年12月)。3.0版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。直到今天,初学者一开始学习 JavaScript,其实就是在学3.0版的语法。
|
||||
前面提到,ECMAScript 1.0 是 1997 年发布的,接下来的两年,连续发布了 ECMAScript 2.0(1998 年 6 月)和 ECMAScript 3.0(1999 年 12 月)。3.0 版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。直到今天,初学者一开始学习 JavaScript,其实就是在学 3.0 版的语法。
|
||||
|
||||
2000年,ECMAScript 4.0 开始酝酿。这个版本最后没有通过,但是它的大部分内容被 ES6 继承了。因此,ES6 制定的起点其实是2000年。
|
||||
2000 年,ECMAScript 4.0 开始酝酿。这个版本最后没有通过,但是它的大部分内容被 ES6 继承了。因此,ES6 制定的起点其实是 2000 年。
|
||||
|
||||
为什么 ES4 没有通过呢?因为这个版本太激进了,对 ES3 做了彻底升级,导致标准委员会的一些成员不愿意接受。ECMA 的第39号技术专家委员会(Technical Committee 39,简称TC39)负责制订 ECMAScript 标准,成员包括 Microsoft、Mozilla、Google 等大公司。
|
||||
为什么 ES4 没有通过呢?因为这个版本太激进了,对 ES3 做了彻底升级,导致标准委员会的一些成员不愿意接受。ECMA 的第 39 号技术专家委员会(Technical Committee 39,简称 TC39)负责制订 ECMAScript 标准,成员包括 Microsoft、Mozilla、Google 等大公司。
|
||||
|
||||
2007年10月,ECMAScript 4.0 版草案发布,本来预计次年8月发布正式版本。但是,各方对于是否通过这个标准,发生了严重分歧。以 Yahoo、Microsoft、Google 为首的大公司,反对 JavaScript 的大幅升级,主张小幅改动;以 JavaScript 创造者Brendan Eich为首的Mozilla公司,则坚持当前的草案。
|
||||
2007 年 10 月,ECMAScript 4.0 版草案发布,本来预计次年 8 月发布正式版本。但是,各方对于是否通过这个标准,发生了严重分歧。以 Yahoo、Microsoft、Google 为首的大公司,反对 JavaScript 的大幅升级,主张小幅改动;以 JavaScript 创造者 Brendan Eich 为首的 Mozilla 公司,则坚持当前的草案。
|
||||
|
||||
2008年7月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA 开会决定,中止 ECMAScript 4.0 的开发,将其中涉及现有功能改善的一小部分,发布为 ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为 Harmony(和谐)。会后不久,ECMAScript 3.1 就改名为 ECMAScript 5。
|
||||
2008 年 7 月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA 开会决定,中止 ECMAScript 4.0 的开发,将其中涉及现有功能改善的一小部分,发布为 ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为 Harmony(和谐)。会后不久,ECMAScript 3.1 就改名为 ECMAScript 5。
|
||||
|
||||
2009年12月,ECMAScript 5.0 版正式发布。Harmony 项目则一分为二,一些较为可行的设想定名为 JavaScript.next 继续开发,后来演变成 ECMAScript 6;一些不是很成熟的设想,则被视为 JavaScript.next.next,在更远的将来再考虑推出。TC39 委员会的总体考虑是,ES5 与 ES3 基本保持兼容,较大的语法修正和新功能加入,将由 JavaScript.next 完成。当时,JavaScript.next 指的是ES6,第六版发布以后,就指 ES7。TC39 的判断是,ES5 会在2013年的年中成为 JavaScript 开发的主流标准,并在此后五年中一直保持这个位置。
|
||||
2009 年 12 月,ECMAScript 5.0 版正式发布。Harmony 项目则一分为二,一些较为可行的设想定名为 JavaScript.next 继续开发,后来演变成 ECMAScript 6;一些不是很成熟的设想,则被视为 JavaScript.next.next,在更远的将来再考虑推出。TC39 委员会的总体考虑是,ES5 与 ES3 基本保持兼容,较大的语法修正和新功能加入,将由 JavaScript.next 完成。当时,JavaScript.next 指的是 ES6,第六版发布以后,就指 ES7。TC39 的判断是,ES5 会在 2013 年的年中成为 JavaScript 开发的主流标准,并在此后五年中一直保持这个位置。
|
||||
|
||||
2011年6月,ECMAscript 5.1 版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。
|
||||
2011 年 6 月,ECMAscript 5.1 版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。
|
||||
|
||||
2013年3月,ECMAScript 6 草案冻结,不再添加新功能。新的功能设想将被放到 ECMAScript 7。
|
||||
2013 年 3 月,ECMAScript 6 草案冻结,不再添加新功能。新的功能设想将被放到 ECMAScript 7。
|
||||
|
||||
2013年12月,ECMAScript 6 草案发布。然后是12个月的讨论期,听取各方反馈。
|
||||
2013 年 12 月,ECMAScript 6 草案发布。然后是 12 个月的讨论期,听取各方反馈。
|
||||
|
||||
2015年6月,ECMAScript 6 正式通过,成为国际标准。从2000年算起,这时已经过去了15年。
|
||||
2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。从 2000 年算起,这时已经过去了 15 年。
|
||||
|
||||
## 部署进度
|
||||
|
||||
各大浏览器的最新版本,对 ES6 的支持可以查看[kangax.github.io/es5-compat-table/es6/](https://kangax.github.io/es5-compat-table/es6/)。随着时间的推移,支持度已经越来越高了,超过90%的 ES6 语法特性都实现了。
|
||||
各大浏览器的最新版本,对 ES6 的支持可以查看[kangax.github.io/es5-compat-table/es6/](https://kangax.github.io/es5-compat-table/es6/)。随着时间的推移,支持度已经越来越高了,超过 90%的 ES6 语法特性都实现了。
|
||||
|
||||
Node 是 JavaScript 的服务器运行环境(runtime)。它对 ES6 的支持度更高。除了那些默认打开的功能,还有一些语法功能已经实现了,但是默认没有打开。使用下面的命令,可以查看 Node 已经实现的 ES6 特性。
|
||||
|
||||
@ -150,11 +150,11 @@ $ npm install --save-dev babel-preset-stage-3
|
||||
}
|
||||
```
|
||||
|
||||
注意,以下所有 Babel工具和模块的使用,都必须先写好`.babelrc`。
|
||||
注意,以下所有 Babel 工具和模块的使用,都必须先写好`.babelrc`。
|
||||
|
||||
### 命令行转码`babel-cli`
|
||||
|
||||
Babel提供`babel-cli`工具,用于命令行转码。
|
||||
Babel 提供`babel-cli`工具,用于命令行转码。
|
||||
|
||||
它的安装命令如下。
|
||||
|
||||
@ -215,9 +215,9 @@ $ npm run build
|
||||
|
||||
### babel-node
|
||||
|
||||
`babel-cli`工具自带一个`babel-node`命令,提供一个支持ES6的REPL环境。它支持Node的REPL环境的所有功能,而且可以直接运行ES6代码。
|
||||
`babel-cli`工具自带一个`babel-node`命令,提供一个支持 ES6 的 REPL 环境。它支持 Node 的 REPL 环境的所有功能,而且可以直接运行 ES6 代码。
|
||||
|
||||
它不用单独安装,而是随`babel-cli`一起安装。然后,执行`babel-node`就进入REPL环境。
|
||||
它不用单独安装,而是随`babel-cli`一起安装。然后,执行`babel-node`就进入 REPL 环境。
|
||||
|
||||
```bash
|
||||
$ babel-node
|
||||
@ -225,7 +225,7 @@ $ babel-node
|
||||
2
|
||||
```
|
||||
|
||||
`babel-node`命令可以直接运行ES6脚本。将上面的代码放入脚本文件`es6.js`,然后直接运行。
|
||||
`babel-node`命令可以直接运行 ES6 脚本。将上面的代码放入脚本文件`es6.js`,然后直接运行。
|
||||
|
||||
```bash
|
||||
$ babel-node es6.js
|
||||
@ -252,7 +252,7 @@ $ npm install --save-dev babel-cli
|
||||
|
||||
### babel-register
|
||||
|
||||
`babel-register`模块改写`require`命令,为它加上一个钩子。此后,每当使用`require`加载`.js`、`.jsx`、`.es`和`.es6`后缀名的文件,就会先用Babel进行转码。
|
||||
`babel-register`模块改写`require`命令,为它加上一个钩子。此后,每当使用`require`加载`.js`、`.jsx`、`.es`和`.es6`后缀名的文件,就会先用 Babel 进行转码。
|
||||
|
||||
```bash
|
||||
$ npm install --save-dev babel-register
|
||||
@ -380,7 +380,7 @@ $ browserify script.js -o bundle.js \
|
||||
|
||||
### 在线转换
|
||||
|
||||
Babel 提供一个[REPL在线编译器](https://babeljs.io/repl/),可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。
|
||||
Babel 提供一个[REPL 在线编译器](https://babeljs.io/repl/),可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。
|
||||
|
||||
### 与其他工具的配合
|
||||
|
||||
@ -445,7 +445,7 @@ Traceur 允许将 ES6 代码直接插入网页。首先,必须在网页头部
|
||||
</script>
|
||||
```
|
||||
|
||||
上面代码中,一共有4个`script`标签。第一个是加载 Traceur 的库文件,第二个和第三个是将这个库文件用于浏览器环境,第四个则是加载用户脚本,这个脚本里面可以使用 ES6 代码。
|
||||
上面代码中,一共有 4 个`script`标签。第一个是加载 Traceur 的库文件,第二个和第三个是将这个库文件用于浏览器环境,第四个则是加载用户脚本,这个脚本里面可以使用 ES6 代码。
|
||||
|
||||
注意,第四个`script`标签的`type`属性的值是`module`,而不是`text/javascript`。这是 Traceur 编译器识别 ES6 代码的标志,编译器会自动将所有`type=module`的代码编译为 ES5,然后再交给浏览器执行。
|
||||
|
||||
@ -495,7 +495,7 @@ Traceur 允许将 ES6 代码直接插入网页。首先,必须在网页头部
|
||||
</script>
|
||||
```
|
||||
|
||||
上面代码中,首先生成Traceur的全局对象`window.System`,然后`System.import`方法可以用来加载 ES6。加载的时候,需要传入一个配置对象`metadata`,该对象的`traceurOptions`属性可以配置支持 ES6 功能。如果设为`experimental: true`,就表示除了 ES6 以外,还支持一些实验性的新功能。
|
||||
上面代码中,首先生成 Traceur 的全局对象`window.System`,然后`System.import`方法可以用来加载 ES6。加载的时候,需要传入一个配置对象`metadata`,该对象的`traceurOptions`属性可以配置支持 ES6 功能。如果设为`experimental: true`,就表示除了 ES6 以外,还支持一些实验性的新功能。
|
||||
|
||||
### 在线转换
|
||||
|
||||
@ -586,4 +586,3 @@ fs.writeFileSync('out.js', result.js);
|
||||
// sourceMap 属性对应 map 文件
|
||||
fs.writeFileSync('out.js.map', result.sourceMap);
|
||||
```
|
||||
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
JavaScript 原有的表示“集合”的数据结构,主要是数组(`Array`)和对象(`Object`),ES6 又添加了`Map`和`Set`。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是`Map`,`Map`的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
|
||||
|
||||
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
|
||||
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
|
||||
|
||||
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令`for...of`循环,Iterator接口主要供`for...of`消费。
|
||||
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令`for...of`循环,Iterator 接口主要供`for...of`消费。
|
||||
|
||||
Iterator 的遍历过程是这样的。
|
||||
|
||||
@ -363,7 +363,7 @@ let arr = ['b', 'c'];
|
||||
let arr = [...iterable];
|
||||
```
|
||||
|
||||
**(3)yield* **
|
||||
**(3)yield\* **
|
||||
|
||||
`yield*`后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
|
||||
|
||||
@ -410,7 +410,7 @@ iterator.next() // { value: "i", done: false }
|
||||
iterator.next() // { value: undefined, done: true }
|
||||
```
|
||||
|
||||
上面代码中,调用`Symbol.iterator`方法返回一个遍历器对象,在这个遍历器上可以调用next方法,实现对于字符串的遍历。
|
||||
上面代码中,调用`Symbol.iterator`方法返回一个遍历器对象,在这个遍历器上可以调用 next 方法,实现对于字符串的遍历。
|
||||
|
||||
可以覆盖原生的`Symbol.iterator`方法,达到修改遍历器行为的目的。
|
||||
|
||||
@ -437,11 +437,11 @@ str[Symbol.iterator] = function() {
|
||||
str // "hi"
|
||||
```
|
||||
|
||||
上面代码中,字符串str的`Symbol.iterator`方法被修改了,所以扩展运算符(`...`)返回的值变成了`bye`,而字符串本身还是`hi`。
|
||||
上面代码中,字符串 str 的`Symbol.iterator`方法被修改了,所以扩展运算符(`...`)返回的值变成了`bye`,而字符串本身还是`hi`。
|
||||
|
||||
## Iterator接口与Generator函数
|
||||
## Iterator 接口与 Generator 函数
|
||||
|
||||
`Symbol.iterator`方法的最简单实现,还是使用下一章要介绍的Generator函数。
|
||||
`Symbol.iterator`方法的最简单实现,还是使用下一章要介绍的 Generator 函数。
|
||||
|
||||
```javascript
|
||||
var myIterable = {};
|
||||
@ -469,9 +469,9 @@ for (let x of obj) {
|
||||
// world
|
||||
```
|
||||
|
||||
上面代码中,`Symbol.iterator`方法几乎不用部署任何代码,只要用yield命令给出每一步的返回值即可。
|
||||
上面代码中,`Symbol.iterator`方法几乎不用部署任何代码,只要用 yield 命令给出每一步的返回值即可。
|
||||
|
||||
## 遍历器对象的return(),throw()
|
||||
## 遍历器对象的 return(),throw()
|
||||
|
||||
遍历器对象除了具有`next`方法,还可以具有`return`方法和`throw`方法。如果你自己写遍历器对象生成函数,那么`next`方法是必须部署的,`return`方法和`throw`方法是否部署是可选的。
|
||||
|
||||
@ -517,13 +517,13 @@ for (let line of readLinesSync(fileName)) {
|
||||
|
||||
注意,`return`方法必须返回一个对象,这是 Generator 规格决定的。
|
||||
|
||||
`throw`方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。请参阅《Generator函数》一章。
|
||||
`throw`方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。请参阅《Generator 函数》一章。
|
||||
|
||||
## for...of循环
|
||||
## for...of 循环
|
||||
|
||||
ES6 借鉴 C++、Java、C# 和 Python 语言,引入了`for...of`循环,作为遍历所有数据结构的统一的方法。
|
||||
|
||||
一个数据结构只要部署了`Symbol.iterator`属性,就被视为具有iterator接口,就可以用`for...of`循环遍历它的成员。也就是说,`for...of`循环内部调用的是数据结构的`Symbol.iterator`方法。
|
||||
一个数据结构只要部署了`Symbol.iterator`属性,就被视为具有 iterator 接口,就可以用`for...of`循环遍历它的成员。也就是说,`for...of`循环内部调用的是数据结构的`Symbol.iterator`方法。
|
||||
|
||||
`for...of`循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如`arguments`对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。
|
||||
|
||||
@ -636,7 +636,7 @@ for (let [key, value] of map) {
|
||||
|
||||
### 计算生成的数据结构
|
||||
|
||||
有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。
|
||||
有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6 的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。
|
||||
|
||||
- `entries()` 返回一个遍历器对象,用来遍历`[键名, 键值]`组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用`entries`方法。
|
||||
- `keys()` 返回一个遍历器对象,用来遍历所有的键名。
|
||||
@ -684,7 +684,7 @@ printArgs('a', 'b');
|
||||
// 'b'
|
||||
```
|
||||
|
||||
对于字符串来说,`for...of`循环还有一个特点,就是会正确识别32位 UTF-16 字符。
|
||||
对于字符串来说,`for...of`循环还有一个特点,就是会正确识别 32 位 UTF-16 字符。
|
||||
|
||||
```javascript
|
||||
for (let x of 'a\uD83D\uDC0A') {
|
||||
@ -809,7 +809,7 @@ for (let value of myArray) {
|
||||
- 不同于`forEach`方法,它可以与`break`、`continue`和`return`配合使用。
|
||||
- 提供了遍历所有数据结构的统一操作接口。
|
||||
|
||||
下面是一个使用break语句,跳出`for...of`循环的例子。
|
||||
下面是一个使用 break 语句,跳出`for...of`循环的例子。
|
||||
|
||||
```javascript
|
||||
for (var n of fibonacci) {
|
||||
@ -819,5 +819,4 @@ for (var n of fibonacci) {
|
||||
}
|
||||
```
|
||||
|
||||
上面的例子,会输出斐波纳契数列小于等于1000的项。如果当前项大于1000,就会使用`break`语句跳出`for...of`循环。
|
||||
|
||||
上面的例子,会输出斐波纳契数列小于等于 1000 的项。如果当前项大于 1000,就会使用`break`语句跳出`for...of`循环。
|
||||
|
15
docs/let.md
15
docs/let.md
@ -43,9 +43,9 @@ for (var i = 0; i < 10; i++) {
|
||||
a[6](); // 10
|
||||
```
|
||||
|
||||
上面代码中,变量`i`是`var`命令声明的,在全局范围内都有效,所以全局只有一个变量`i`。每一次循环,变量`i`的值都会发生改变,而循环内被赋给数组`a`的函数内部的`console.log(i)`,里面的`i`指向的就是全局的`i`。也就是说,所有数组`a`的成员里面的`i`,指向的都是同一个`i`,导致运行时输出的是最后一轮的`i`的值,也就是10。
|
||||
上面代码中,变量`i`是`var`命令声明的,在全局范围内都有效,所以全局只有一个变量`i`。每一次循环,变量`i`的值都会发生改变,而循环内被赋给数组`a`的函数内部的`console.log(i)`,里面的`i`指向的就是全局的`i`。也就是说,所有数组`a`的成员里面的`i`,指向的都是同一个`i`,导致运行时输出的是最后一轮的`i`的值,也就是 10。
|
||||
|
||||
如果使用`let`,声明的变量仅在块级作用域内有效,最后输出的是6。
|
||||
如果使用`let`,声明的变量仅在块级作用域内有效,最后输出的是 6。
|
||||
|
||||
```javascript
|
||||
var a = [];
|
||||
@ -71,7 +71,7 @@ for (let i = 0; i < 3; i++) {
|
||||
// abc
|
||||
```
|
||||
|
||||
上面代码正确运行,输出了3次`abc`。这表明函数内部的变量`i`与循环变量`i`不在同一个作用域,有各自单独的作用域。
|
||||
上面代码正确运行,输出了 3 次`abc`。这表明函数内部的变量`i`与循环变量`i`不在同一个作用域,有各自单独的作用域。
|
||||
|
||||
### 不存在变量提升
|
||||
|
||||
@ -106,7 +106,7 @@ if (true) {
|
||||
|
||||
上面代码中,存在全局变量`tmp`,但是块级作用域内`let`又声明了一个局部变量`tmp`,导致后者绑定这个块级作用域,所以在`let`声明变量前,对`tmp`赋值会报错。
|
||||
|
||||
ES6明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
|
||||
ES6 明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
|
||||
|
||||
总之,在代码块内,使用`let`命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
|
||||
|
||||
@ -262,7 +262,7 @@ function f1() {
|
||||
}
|
||||
```
|
||||
|
||||
上面的函数有两个代码块,都声明了变量`n`,运行后输出5。这表示外层代码块不受内层代码块的影响。如果两次都使用`var`定义变量`n`,最后输出的值才是10。
|
||||
上面的函数有两个代码块,都声明了变量`n`,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用`var`定义变量`n`,最后输出的值才是 10。
|
||||
|
||||
ES6 允许块级作用域的任意嵌套。
|
||||
|
||||
@ -359,7 +359,7 @@ function f() { console.log('I am outside!'); }
|
||||
|
||||
ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于`let`,对作用域之外没有影响。但是,如果你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为什么呢?
|
||||
|
||||
原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6在[附录B](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics)里面规定,浏览器的实现可以不遵守上面的规定,有自己的[行为方式](http://stackoverflow.com/questions/31419897/what-are-the-precise-semantics-of-block-level-functions-in-es6)。
|
||||
原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在[附录 B](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics)里面规定,浏览器的实现可以不遵守上面的规定,有自己的[行为方式](http://stackoverflow.com/questions/31419897/what-are-the-precise-semantics-of-block-level-functions-in-es6)。
|
||||
|
||||
- 允许在块级作用域内声明函数。
|
||||
- 函数声明类似于`var`,即会提升到全局作用域或函数作用域的头部。
|
||||
@ -571,7 +571,7 @@ var constantize = (obj) => {
|
||||
|
||||
### ES6 声明变量的六种方法
|
||||
|
||||
ES5 只有两种声明变量的方法:`var`命令和`function`命令。ES6 除了添加`let`和`const`命令,后面章节还会提到,另外两种声明变量的方法:`import`命令和`class`命令。所以,ES6 一共有6种声明变量的方法。
|
||||
ES5 只有两种声明变量的方法:`var`命令和`function`命令。ES6 除了添加`let`和`const`命令,后面章节还会提到,另外两种声明变量的方法:`import`命令和`class`命令。所以,ES6 一共有 6 种声明变量的方法。
|
||||
|
||||
## 顶层对象的属性
|
||||
|
||||
@ -662,4 +662,3 @@ const global = getGlobal();
|
||||
```
|
||||
|
||||
上面代码将顶层对象放入变量`global`。
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
# Mixin
|
||||
|
||||
JavaScript语言的设计是单一继承,即子类只能继承一个父类,不允许继承多个父类。这种设计保证了对象继承的层次结构是树状的,而不是复杂的[网状结构](https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem)。
|
||||
JavaScript 语言的设计是单一继承,即子类只能继承一个父类,不允许继承多个父类。这种设计保证了对象继承的层次结构是树状的,而不是复杂的[网状结构](https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem)。
|
||||
|
||||
但是,这大大降低了编程的灵活性。因为实际开发中,有时不可避免,子类需要继承多个父类。举例来说,“猫”可以继承“哺乳类动物”,也可以继承“宠物”。
|
||||
|
||||
各种单一继承的编程语言,有不同的多重继承解决方案。比如,Java语言也是子类只能继承一个父类,但是还允许继承多个界面(interface),这样就间接实现了多重继承。Interface与父类一样,也是一个类,只不过它只定义接口(method signature),不定义实现,因此又被称为“抽象类”。凡是继承于Interface的方法,都必须自己定义实现,否则就会报错。这样就避免了多重继承的最大问题:多个父类的同名方法的碰撞(naming collision)。
|
||||
各种单一继承的编程语言,有不同的多重继承解决方案。比如,Java 语言也是子类只能继承一个父类,但是还允许继承多个界面(interface),这样就间接实现了多重继承。Interface 与父类一样,也是一个类,只不过它只定义接口(method signature),不定义实现,因此又被称为“抽象类”。凡是继承于 Interface 的方法,都必须自己定义实现,否则就会报错。这样就避免了多重继承的最大问题:多个父类的同名方法的碰撞(naming collision)。
|
||||
|
||||
JavaScript语言没有采用Interface的方案,而是通过代理(delegation)实现了从其他类引入方法。
|
||||
JavaScript 语言没有采用 Interface 的方案,而是通过代理(delegation)实现了从其他类引入方法。
|
||||
|
||||
```javascript
|
||||
var Enumerable_first = function () {
|
||||
@ -24,15 +24,15 @@ list.first() // "foo"
|
||||
|
||||
## 含义
|
||||
|
||||
Mixin这个名字来自于冰淇淋,在基本口味的冰淇淋上面混入其他口味,这就叫做Mix-in。
|
||||
Mixin 这个名字来自于冰淇淋,在基本口味的冰淇淋上面混入其他口味,这就叫做 Mix-in。
|
||||
|
||||
它允许向一个类里面注入一些代码,使得一个类的功能能够“混入”另一个类。实质上是多重继承的一种解决方案,但是避免了多重继承的复杂性,而且有利于代码复用。
|
||||
|
||||
Mixin就是一个正常的类,不仅定义了接口,还定义了接口的实现。
|
||||
Mixin 就是一个正常的类,不仅定义了接口,还定义了接口的实现。
|
||||
|
||||
子类通过在`this`对象上面绑定方法,达到多重继承的目的。
|
||||
|
||||
很多库提供了Mixin功能。下面以Lodash为例。
|
||||
很多库提供了 Mixin 功能。下面以 Lodash 为例。
|
||||
|
||||
```javascript
|
||||
function vowels(string) {
|
||||
@ -44,9 +44,9 @@ _.mixin(obj, {vowels: vowels})
|
||||
obj.vowels() // true
|
||||
```
|
||||
|
||||
上面代码通过Lodash库的`_.mixin`方法,让`obj`对象继承了`vowels`方法。
|
||||
上面代码通过 Lodash 库的`_.mixin`方法,让`obj`对象继承了`vowels`方法。
|
||||
|
||||
Underscore的类似方法是`_.extend`。
|
||||
Underscore 的类似方法是`_.extend`。
|
||||
|
||||
```javascript
|
||||
var Person = function (fName, lName) {
|
||||
@ -90,7 +90,7 @@ function extend(destination, source) {
|
||||
|
||||
## Trait
|
||||
|
||||
Trait是另外一种多重继承的解决方案。它与Mixin很相似,但是有一些细微的差别。
|
||||
Trait 是另外一种多重继承的解决方案。它与 Mixin 很相似,但是有一些细微的差别。
|
||||
|
||||
- Mixin可以包含状态(state),Trait不包含,即Trait里面的方法都是互不相干,可以线性包含的。比如,`Trait1`包含方法`A`和`B`,`Trait2`继承了`Trait1`,同时还包含一个自己的方法`C`,实际上就等同于直接包含方法`A`、`B`、`C`。
|
||||
- 对于同名方法的碰撞,Mixin包含了解决规则,Trait则是报错。
|
||||
- Mixin 可以包含状态(state),Trait 不包含,即 Trait 里面的方法都是互不相干,可以线性包含的。比如,`Trait1`包含方法`A`和`B`,`Trait2`继承了`Trait1`,同时还包含一个自己的方法`C`,实际上就等同于直接包含方法`A`、`B`、`C`。
|
||||
- 对于同名方法的碰撞,Mixin 包含了解决规则,Trait 则是报错。
|
||||
|
@ -194,7 +194,7 @@ console.log(foo);
|
||||
setTimeout(() => console.log(foo), 500);
|
||||
```
|
||||
|
||||
上面代码中,`m1.js`的变量`foo`,在刚加载时等于`bar`,过了500毫秒,又变为等于`baz`。
|
||||
上面代码中,`m1.js`的变量`foo`,在刚加载时等于`bar`,过了 500 毫秒,又变为等于`baz`。
|
||||
|
||||
让我们看看,`m2.js`能否正确读取这个变化。
|
||||
|
||||
@ -694,7 +694,7 @@ export function odd(n) {
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`even.js`里面的函数`even`有一个参数`n`,只要不等于0,就会减去1,传入加载的`odd()`。`odd.js`也会做类似操作。
|
||||
上面代码中,`even.js`里面的函数`even`有一个参数`n`,只要不等于 0,就会减去 1,传入加载的`odd()`。`odd.js`也会做类似操作。
|
||||
|
||||
运行上面这段代码,结果如下。
|
||||
|
||||
@ -711,7 +711,7 @@ true
|
||||
17
|
||||
```
|
||||
|
||||
上面代码中,参数`n`从10变为0的过程中,`even()`一共会执行6次,所以变量`counter`等于6。第二次调用`even()`时,参数`n`从20变为0,`even()`一共会执行11次,加上前面的6次,所以变量`counter`等于17。
|
||||
上面代码中,参数`n`从 10 变为 0 的过程中,`even()`一共会执行 6 次,所以变量`counter`等于 6。第二次调用`even()`时,参数`n`从 20 变为 0,`even()`一共会执行 11 次,加上前面的 6 次,所以变量`counter`等于 17。
|
||||
|
||||
这个例子要是改写成 CommonJS,就根本无法执行,会报错。
|
||||
|
||||
@ -785,7 +785,7 @@ $ compile-modules convert -o out.js file1.js
|
||||
</script>
|
||||
```
|
||||
|
||||
上面代码中的`./app`,指的是当前目录下的app.js文件。它可以是ES6模块文件,`System.import`会自动将其转码。
|
||||
上面代码中的`./app`,指的是当前目录下的 app.js 文件。它可以是 ES6 模块文件,`System.import`会自动将其转码。
|
||||
|
||||
需要注意的是,`System.import`使用异步加载,返回一个 Promise 对象,可以针对这个对象编程。下面是一个模块文件。
|
||||
|
||||
@ -812,4 +812,3 @@ System.import('app/es6-file').then(function(m) {
|
||||
```
|
||||
|
||||
上面代码中,`System.import`方法返回的是一个 Promise 对象,所以可以用`then`方法指定回调函数。
|
||||
|
||||
|
@ -19,7 +19,7 @@ let exists = _fs.exists;
|
||||
let readfile = _fs.readfile;
|
||||
```
|
||||
|
||||
上面代码的实质是整体加载`fs`模块(即加载`fs`的所有方法),生成一个对象(`_fs`),然后再从这个对象上面读取3个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
|
||||
上面代码的实质是整体加载`fs`模块(即加载`fs`的所有方法),生成一个对象(`_fs`),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
|
||||
|
||||
ES6 模块不是对象,而是通过`export`命令显式指定输出的代码,再通过`import`命令输入。
|
||||
|
||||
@ -28,7 +28,7 @@ ES6 模块不是对象,而是通过`export`命令显式指定输出的代码
|
||||
import { stat, exists, readFile } from 'fs';
|
||||
```
|
||||
|
||||
上面代码的实质是从`fs`模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
|
||||
上面代码的实质是从`fs`模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
|
||||
|
||||
由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
|
||||
|
||||
@ -50,7 +50,7 @@ ES6 的模块自动采用严格模式,不管你有没有在模块头部加上`
|
||||
- 函数的参数不能有同名属性,否则报错
|
||||
- 不能使用`with`语句
|
||||
- 不能对只读属性赋值,否则报错
|
||||
- 不能使用前缀0表示八进制数,否则报错
|
||||
- 不能使用前缀 0 表示八进制数,否则报错
|
||||
- 不能删除不可删除的属性,否则报错
|
||||
- 不能删除变量`delete prop`,会报错,只能删除属性`delete global[prop]`
|
||||
- `eval`不会在它的外层作用域引入变量
|
||||
@ -130,7 +130,7 @@ var m = 1;
|
||||
export m;
|
||||
```
|
||||
|
||||
上面两种写法都会报错,因为没有提供对外的接口。第一种写法直接输出1,第二种写法通过变量`m`,还是直接输出1。`1`只是一个值,不是接口。正确的写法是下面这样。
|
||||
上面两种写法都会报错,因为没有提供对外的接口。第一种写法直接输出 1,第二种写法通过变量`m`,还是直接输出 1。`1`只是一个值,不是接口。正确的写法是下面这样。
|
||||
|
||||
```javascript
|
||||
// 写法一
|
||||
@ -169,11 +169,11 @@ export var foo = 'bar';
|
||||
setTimeout(() => foo = 'baz', 500);
|
||||
```
|
||||
|
||||
上面代码输出变量`foo`,值为`bar`,500毫秒之后变成`baz`。
|
||||
上面代码输出变量`foo`,值为`bar`,500 毫秒之后变成`baz`。
|
||||
|
||||
这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新,详见下文《Module 的加载实现》一节。
|
||||
|
||||
最后,`export`命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的`import`命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了ES6模块的设计初衷。
|
||||
最后,`export`命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的`import`命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。
|
||||
|
||||
```javascript
|
||||
function foo() {
|
||||
@ -784,4 +784,3 @@ async function main() {
|
||||
}
|
||||
main();
|
||||
```
|
||||
|
||||
|
@ -132,7 +132,7 @@ Number.parseFloat === parseFloat // true
|
||||
|
||||
## Number.isInteger()
|
||||
|
||||
`Number.isInteger()`用来判断一个值是否为整数。需要注意的是,在 JavaScript 内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值。
|
||||
`Number.isInteger()`用来判断一个值是否为整数。需要注意的是,在 JavaScript 内部,整数和浮点数是同样的储存方法,所以 3 和 3.0 被视为同一个值。
|
||||
|
||||
```javascript
|
||||
Number.isInteger(25) // true
|
||||
@ -164,9 +164,9 @@ ES5 可以通过下面的代码,部署`Number.isInteger()`。
|
||||
|
||||
## Number.EPSILON
|
||||
|
||||
ES6 在`Number`对象上面,新增一个极小的常量`Number.EPSILON`。根据规格,它表示1与大于1的最小浮点数之间的差。
|
||||
ES6 在`Number`对象上面,新增一个极小的常量`Number.EPSILON`。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。
|
||||
|
||||
对于64位浮点数来说,大于1的最小浮点数相当于二进制的`1.00..001`,小数点后面有连续51个零。这个值减去1之后,就等于2的-52次方。
|
||||
对于 64 位浮点数来说,大于 1 的最小浮点数相当于二进制的`1.00..001`,小数点后面有连续 51 个零。这个值减去 1 之后,就等于 2 的-52 次方。
|
||||
|
||||
```javascript
|
||||
Number.EPSILON === Math.pow(2, -52)
|
||||
@ -198,7 +198,7 @@ Number.EPSILON.toFixed(20)
|
||||
0.1 + 0.2 === 0.3 // false
|
||||
```
|
||||
|
||||
`Number.EPSILON`可以用来设置“能够接受的误差范围”。比如,误差范围设为2的-50次方(即`Number.EPSILON * Math.pow(2, 2)`),即如果两个浮点数的差小于这个值,我们就认为这两个浮点数相等。
|
||||
`Number.EPSILON`可以用来设置“能够接受的误差范围”。比如,误差范围设为 2 的-50 次方(即`Number.EPSILON * Math.pow(2, 2)`),即如果两个浮点数的差小于这个值,我们就认为这两个浮点数相等。
|
||||
|
||||
```javascript
|
||||
5.551115123125783e-17 < Number.EPSILON * Math.pow(2, 2)
|
||||
@ -235,9 +235,9 @@ Math.pow(2, 53) === Math.pow(2, 53) + 1
|
||||
// true
|
||||
```
|
||||
|
||||
上面代码中,超出2的53次方之后,一个数就不精确了。
|
||||
上面代码中,超出 2 的 53 次方之后,一个数就不精确了。
|
||||
|
||||
ES6引入了`Number.MAX_SAFE_INTEGER`和`Number.MIN_SAFE_INTEGER`这两个常量,用来表示这个范围的上下限。
|
||||
ES6 引入了`Number.MAX_SAFE_INTEGER`和`Number.MIN_SAFE_INTEGER`这两个常量,用来表示这个范围的上下限。
|
||||
|
||||
```javascript
|
||||
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
|
||||
@ -251,7 +251,7 @@ Number.MIN_SAFE_INTEGER === -9007199254740991
|
||||
// true
|
||||
```
|
||||
|
||||
上面代码中,可以看到JavaScript能够精确表示的极限。
|
||||
上面代码中,可以看到 JavaScript 能够精确表示的极限。
|
||||
|
||||
`Number.isSafeInteger()`则是用来判断一个整数是否落在这个范围之内。
|
||||
|
||||
@ -326,9 +326,9 @@ trusty(1, 2, 3)
|
||||
// 3
|
||||
```
|
||||
|
||||
## Math对象的扩展
|
||||
## Math 对象的扩展
|
||||
|
||||
ES6在Math对象上新增了17个与数学相关的方法。所有这些方法都是静态方法,只能在Math对象上调用。
|
||||
ES6 在 Math 对象上新增了 17 个与数学相关的方法。所有这些方法都是静态方法,只能在 Math 对象上调用。
|
||||
|
||||
### Math.trunc()
|
||||
|
||||
@ -376,7 +376,7 @@ Math.trunc = Math.trunc || function(x) {
|
||||
|
||||
- 参数为正数,返回`+1`;
|
||||
- 参数为负数,返回`-1`;
|
||||
- 参数为0,返回`0`;
|
||||
- 参数为 0,返回`0`;
|
||||
- 参数为-0,返回`-0`;
|
||||
- 其他值,返回`NaN`。
|
||||
|
||||
@ -442,7 +442,7 @@ Math.cbrt = Math.cbrt || function(x) {
|
||||
|
||||
### Math.clz32()
|
||||
|
||||
JavaScript的整数使用32位二进制形式表示,`Math.clz32`方法返回一个数的32位无符号整数形式有多少个前导0。
|
||||
JavaScript 的整数使用 32 位二进制形式表示,`Math.clz32`方法返回一个数的 32 位无符号整数形式有多少个前导 0。
|
||||
|
||||
```javascript
|
||||
Math.clz32(0) // 32
|
||||
@ -452,9 +452,9 @@ Math.clz32(0b01000000000000000000000000000000) // 1
|
||||
Math.clz32(0b00100000000000000000000000000000) // 2
|
||||
```
|
||||
|
||||
上面代码中,0的二进制形式全为0,所以有32个前导0;1的二进制形式是`0b1`,只占1位,所以32位之中有31个前导0;1000的二进制形式是`0b1111101000`,一共有10位,所以32位之中有22个前导0。
|
||||
上面代码中,0 的二进制形式全为 0,所以有 32 个前导 0;1 的二进制形式是`0b1`,只占 1 位,所以 32 位之中有 31 个前导 0;1000 的二进制形式是`0b1111101000`,一共有 10 位,所以 32 位之中有 22 个前导 0。
|
||||
|
||||
`clz32`这个函数名就来自”count leading zero bits in 32-bit binary representation of a number“(计算一个数的32位二进制形式的前导0的个数)的缩写。
|
||||
`clz32`这个函数名就来自”count leading zero bits in 32-bit binary representation of a number“(计算一个数的 32 位二进制形式的前导 0 的个数)的缩写。
|
||||
|
||||
左移运算符(`<<`)与`Math.clz32`方法直接相关。
|
||||
|
||||
@ -488,7 +488,7 @@ Math.clz32(true) // 31
|
||||
|
||||
### Math.imul()
|
||||
|
||||
`Math.imul`方法返回两个数以32位带符号整数形式相乘的结果,返回的也是一个32位的带符号整数。
|
||||
`Math.imul`方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
|
||||
|
||||
```javascript
|
||||
Math.imul(2, 4) // 8
|
||||
@ -496,13 +496,13 @@ Math.imul(-1, 8) // -8
|
||||
Math.imul(-2, -2) // 4
|
||||
```
|
||||
|
||||
如果只考虑最后32位,大多数情况下,`Math.imul(a, b)`与`a * b`的结果是相同的,即该方法等同于`(a * b)|0`的效果(超过32位的部分溢出)。之所以需要部署这个方法,是因为JavaScript有精度限制,超过2的53次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,`Math.imul`方法可以返回正确的低位数值。
|
||||
如果只考虑最后 32 位,大多数情况下,`Math.imul(a, b)`与`a * b`的结果是相同的,即该方法等同于`(a * b)|0`的效果(超过 32 位的部分溢出)。之所以需要部署这个方法,是因为 JavaScript 有精度限制,超过 2 的 53 次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,`Math.imul`方法可以返回正确的低位数值。
|
||||
|
||||
```javascript
|
||||
(0x7fffffff * 0x7fffffff)|0 // 0
|
||||
```
|
||||
|
||||
上面这个乘法算式,返回结果为0。但是由于这两个二进制数的最低位都是1,所以这个结果肯定是不正确的,因为根据二进制乘法,计算结果的二进制最低位应该也是1。这个错误就是因为它们的乘积超过了2的53次方,JavaScript无法保存额外的精度,就把低位的值都变成了0。`Math.imul`方法可以返回正确的值1。
|
||||
上面这个乘法算式,返回结果为 0。但是由于这两个二进制数的最低位都是 1,所以这个结果肯定是不正确的,因为根据二进制乘法,计算结果的二进制最低位应该也是 1。这个错误就是因为它们的乘积超过了 2 的 53 次方,JavaScript 无法保存额外的精度,就把低位的值都变成了 0。`Math.imul`方法可以返回正确的值 1。
|
||||
|
||||
```javascript
|
||||
Math.imul(0x7fffffff, 0x7fffffff) // 1
|
||||
@ -510,7 +510,7 @@ Math.imul(0x7fffffff, 0x7fffffff) // 1
|
||||
|
||||
### Math.fround()
|
||||
|
||||
Math.fround方法返回一个数的单精度浮点数形式。
|
||||
Math.fround 方法返回一个数的单精度浮点数形式。
|
||||
|
||||
```javascript
|
||||
Math.fround(0) // 0
|
||||
@ -520,7 +520,7 @@ Math.fround(1.5) // 1.5
|
||||
Math.fround(NaN) // NaN
|
||||
```
|
||||
|
||||
对于整数来说,`Math.fround`方法返回结果不会有任何不同,区别主要是那些无法用64个二进制位精确表示的小数。这时,`Math.fround`方法会返回最接近这个小数的单精度浮点数。
|
||||
对于整数来说,`Math.fround`方法返回结果不会有任何不同,区别主要是那些无法用 64 个二进制位精确表示的小数。这时,`Math.fround`方法会返回最接近这个小数的单精度浮点数。
|
||||
|
||||
对于没有部署这个方法的环境,可以用下面的代码模拟。
|
||||
|
||||
@ -544,17 +544,17 @@ Math.hypot(3, 4, '5'); // 7.0710678118654755
|
||||
Math.hypot(-3); // 3
|
||||
```
|
||||
|
||||
上面代码中,3的平方加上4的平方,等于5的平方。
|
||||
上面代码中,3 的平方加上 4 的平方,等于 5 的平方。
|
||||
|
||||
如果参数不是数值,`Math.hypot`方法会将其转为数值。只要有一个参数无法转为数值,就会返回NaN。
|
||||
如果参数不是数值,`Math.hypot`方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。
|
||||
|
||||
### 对数方法
|
||||
|
||||
ES6新增了4个对数相关方法。
|
||||
ES6 新增了 4 个对数相关方法。
|
||||
|
||||
**(1) Math.expm1()**
|
||||
|
||||
`Math.expm1(x)`返回e<sup>x</sup> - 1,即`Math.exp(x) - 1`。
|
||||
`Math.expm1(x)`返回 e<sup>x</sup> - 1,即`Math.exp(x) - 1`。
|
||||
|
||||
```javascript
|
||||
Math.expm1(-1) // -0.6321205588285577
|
||||
@ -591,7 +591,7 @@ Math.log1p = Math.log1p || function(x) {
|
||||
|
||||
**(3)Math.log10()**
|
||||
|
||||
`Math.log10(x)`返回以10为底的`x`的对数。如果`x`小于0,则返回NaN。
|
||||
`Math.log10(x)`返回以 10 为底的`x`的对数。如果`x`小于 0,则返回 NaN。
|
||||
|
||||
```javascript
|
||||
Math.log10(2) // 0.3010299956639812
|
||||
@ -611,7 +611,7 @@ Math.log10 = Math.log10 || function(x) {
|
||||
|
||||
**(4)Math.log2()**
|
||||
|
||||
`Math.log2(x)`返回以2为底的`x`的对数。如果`x`小于0,则返回NaN。
|
||||
`Math.log2(x)`返回以 2 为底的`x`的对数。如果`x`小于 0,则返回 NaN。
|
||||
|
||||
```javascript
|
||||
Math.log2(3) // 1.584962500721156
|
||||
@ -633,7 +633,7 @@ Math.log2 = Math.log2 || function(x) {
|
||||
|
||||
### 双曲函数方法
|
||||
|
||||
ES6新增了6个双曲函数方法。
|
||||
ES6 新增了 6 个双曲函数方法。
|
||||
|
||||
- `Math.sinh(x)` 返回`x`的双曲正弦(hyperbolic sine)
|
||||
- `Math.cosh(x)` 返回`x`的双曲余弦(hyperbolic cosine)
|
||||
@ -650,7 +650,7 @@ ES6新增了6个双曲函数方法。
|
||||
Math.sign(-0) // -0
|
||||
```
|
||||
|
||||
这导致对于判断符号位的正负,`Math.sign()`不是很有用。JavaScript 内部使用64位浮点数(国际标准IEEE 754)表示数值,IEEE 754规定第一位是符号位,`0`表示正数,`1`表示负数。所以会有两种零,`+0`是符号位为`0`时的零值,`-0`是符号位为`1`时的零值。实际编程中,判断一个值是`+0`还是`-0`非常麻烦,因为它们是相等的。
|
||||
这导致对于判断符号位的正负,`Math.sign()`不是很有用。JavaScript 内部使用 64 位浮点数(国际标准 IEEE 754)表示数值,IEEE 754 规定第一位是符号位,`0`表示正数,`1`表示负数。所以会有两种零,`+0`是符号位为`0`时的零值,`-0`是符号位为`1`时的零值。实际编程中,判断一个值是`+0`还是`-0`非常麻烦,因为它们是相等的。
|
||||
|
||||
```javascript
|
||||
+0 === -0 // true
|
||||
@ -711,7 +711,7 @@ Math.pow(99, 99)
|
||||
|
||||
### 简介
|
||||
|
||||
JavaScript 所有数字都保存成64位浮点数,这决定了整数的精确程度只能到53个二进制位。大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。
|
||||
JavaScript 所有数字都保存成 64 位浮点数,这决定了整数的精确程度只能到 53 个二进制位。大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。
|
||||
|
||||
现在有一个[提案](https://github.com/tc39/proposal-bigint),引入了新的数据类型 Integer(整数),来解决这个问题。整数类型的数据只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
|
||||
|
||||
@ -764,7 +764,7 @@ Integer('abc') // SyntaxError
|
||||
// 1n
|
||||
```
|
||||
|
||||
几乎所有的 Number 运算符都可以用在 Integer,但是有两个除外:不带符号的右移位运算符`>>>`和一元的求正运算符`+`,使用时会报错。前者是因为`>>>`要求最高位补0,但是 Integer 类型没有最高位,导致这个运算符无意义。后者是因为一元运算符`+`在 asm.js 里面总是返回 Number 类型或者报错。
|
||||
几乎所有的 Number 运算符都可以用在 Integer,但是有两个除外:不带符号的右移位运算符`>>>`和一元的求正运算符`+`,使用时会报错。前者是因为`>>>`要求最高位补 0,但是 Integer 类型没有最高位,导致这个运算符无意义。后者是因为一元运算符`+`在 asm.js 里面总是返回 Number 类型或者报错。
|
||||
|
||||
Integer 类型不能与 Number 类型进行混合运算。
|
||||
|
||||
@ -791,4 +791,3 @@ Integer 类型不能与 Number 类型进行混合运算。
|
||||
0n === 0
|
||||
// false
|
||||
```
|
||||
|
||||
|
@ -479,7 +479,7 @@ Object.assign([1, 2, 3], [4, 5])
|
||||
// [4, 5, 3]
|
||||
```
|
||||
|
||||
上面代码中,`Object.assign`把数组视为属性名为0、1、2的对象,因此源数组的0号属性`4`覆盖了目标数组的0号属性`1`。
|
||||
上面代码中,`Object.assign`把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性`4`覆盖了目标数组的 0 号属性`1`。
|
||||
|
||||
**(4)取值函数的处理**
|
||||
|
||||
@ -604,7 +604,7 @@ processContent({ url: {port: 8000} })
|
||||
// }
|
||||
```
|
||||
|
||||
上面代码的原意是将`url.port`改成8000,`url.host`不变。实际结果却是`options.url`覆盖掉`DEFAULTS.url`,所以`url.host`就不存在了。
|
||||
上面代码的原意是将`url.port`改成 8000,`url.host`不变。实际结果却是`options.url`覆盖掉`DEFAULTS.url`,所以`url.host`就不存在了。
|
||||
|
||||
## 属性的可枚举性和遍历
|
||||
|
||||
@ -655,7 +655,7 @@ Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
|
||||
|
||||
### 属性的遍历
|
||||
|
||||
ES6 一共有5种方法可以遍历对象的属性。
|
||||
ES6 一共有 5 种方法可以遍历对象的属性。
|
||||
|
||||
**(1)for...in**
|
||||
|
||||
@ -677,7 +677,7 @@ ES6 一共有5种方法可以遍历对象的属性。
|
||||
|
||||
`Reflect.ownKeys`返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
|
||||
|
||||
以上的5种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
|
||||
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
|
||||
|
||||
- 首先遍历所有数值键,按照数值升序排列。
|
||||
- 其次遍历所有字符串键,按照加入时间升序排列。
|
||||
@ -1489,10 +1489,10 @@ const firstName = message?.body?.user?.firstName || 'default';
|
||||
|
||||
“Null 传导运算符”有四种用法。
|
||||
|
||||
- `obj?.prop` // 读取对象属性
|
||||
- `obj?.[expr]` // 同上
|
||||
- `obj?.prop` // 读取对象属性
|
||||
- `obj?.[expr]` // 同上
|
||||
- `func?.(...args)` // 函数或对象方法的调用
|
||||
- `new C?.(...args)` // 构造函数的调用
|
||||
- `new C?.(...args)` // 构造函数的调用
|
||||
|
||||
传导运算符之所以写成`obj?.prop`,而不是`obj?prop`,是为了方便编译器能够区分三元运算符`?:`(比如`obj?prop:123`)。
|
||||
|
||||
@ -1510,4 +1510,3 @@ a?.b = 42
|
||||
// 如果 a 是 null 或 undefined,下面的语句不产生任何效果
|
||||
delete a?.b
|
||||
```
|
||||
|
||||
|
@ -181,7 +181,7 @@ p2
|
||||
// Error: fail
|
||||
```
|
||||
|
||||
上面代码中,`p1`是一个Promise,3秒之后变为`rejected`。`p2`的状态在1秒之后改变,`resolve`方法返回的是`p1`。由于`p2`返回的是另一个 Promise,导致`p2`自己的状态无效了,由`p1`的状态决定`p2`的状态。所以,后面的`then`语句都变成针对后者(`p1`)。又过了2秒,`p1`变为`rejected`,导致触发`catch`方法指定的回调函数。
|
||||
上面代码中,`p1`是一个 Promise,3 秒之后变为`rejected`。`p2`的状态在 1 秒之后改变,`resolve`方法返回的是`p1`。由于`p2`返回的是另一个 Promise,导致`p2`自己的状态无效了,由`p1`的状态决定`p2`的状态。所以,后面的`then`语句都变成针对后者(`p1`)。又过了 2 秒,`p1`变为`rejected`,导致触发`catch`方法指定的回调函数。
|
||||
|
||||
注意,调用`resolve`或`reject`并不会终结 Promise 的参数函数的执行。
|
||||
|
||||
@ -311,7 +311,7 @@ promise.catch(function(error) {
|
||||
|
||||
比较上面两种写法,可以发现`reject`方法的作用,等同于抛出错误。
|
||||
|
||||
如果Promise状态已经变成`resolved`,再抛出错误是无效的。
|
||||
如果 Promise 状态已经变成`resolved`,再抛出错误是无效的。
|
||||
|
||||
```javascript
|
||||
const promise = new Promise(function(resolve, reject) {
|
||||
@ -338,9 +338,9 @@ getJSON('/post/1.json').then(function(post) {
|
||||
});
|
||||
```
|
||||
|
||||
上面代码中,一共有三个Promise对象:一个由`getJSON`产生,两个由`then`产生。它们之中任何一个抛出的错误,都会被最后一个`catch`捕获。
|
||||
上面代码中,一共有三个 Promise 对象:一个由`getJSON`产生,两个由`then`产生。它们之中任何一个抛出的错误,都会被最后一个`catch`捕获。
|
||||
|
||||
一般来说,不要在`then`方法里面定义Reject状态的回调函数(即`then`的第二个参数),总是使用`catch`方法。
|
||||
一般来说,不要在`then`方法里面定义 Reject 状态的回调函数(即`then`的第二个参数),总是使用`catch`方法。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
@ -382,7 +382,7 @@ setTimeout(() => { console.log(123) }, 2000);
|
||||
// 123
|
||||
```
|
||||
|
||||
上面代码中,`someAsyncThing`函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示`ReferenceError: x is not defined`,但是不会退出进程、终止脚本执行,2秒之后还是会输出`123`。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
|
||||
上面代码中,`someAsyncThing`函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示`ReferenceError: x is not defined`,但是不会退出进程、终止脚本执行,2 秒之后还是会输出`123`。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
|
||||
|
||||
这个脚本放在服务器执行,退出码就是`0`(即表示执行成功)。不过,Node 有一个`unhandledRejection`事件,专门监听未捕获的`reject`错误,上面的脚本会触发这个事件的监听函数,可以在监听函数里面抛出错误。
|
||||
|
||||
@ -394,7 +394,7 @@ process.on('unhandledRejection', function (err, p) {
|
||||
|
||||
上面代码中,`unhandledRejection`事件的监听函数有两个参数,第一个是错误对象,第二个是报错的 Promise 实例,它可以用来了解发生错误的环境信息。
|
||||
|
||||
注意,Node 有计划在未来废除`unhandledRejection`事件。如果 Promise 内部有未捕获的错误,会直接终止进程,并且进程的退出码不为0。
|
||||
注意,Node 有计划在未来废除`unhandledRejection`事件。如果 Promise 内部有未捕获的错误,会直接终止进程,并且进程的退出码不为 0。
|
||||
|
||||
再看下面的例子。
|
||||
|
||||
@ -517,7 +517,7 @@ Promise.all(promises).then(function (posts) {
|
||||
});
|
||||
```
|
||||
|
||||
上面代码中,`promises`是包含6个 Promise 实例的数组,只有这6个实例的状态都变成`fulfilled`,或者其中有一个变为`rejected`,才会调用`Promise.all`方法后面的回调函数。
|
||||
上面代码中,`promises`是包含 6 个 Promise 实例的数组,只有这 6 个实例的状态都变成`fulfilled`,或者其中有一个变为`rejected`,才会调用`Promise.all`方法后面的回调函数。
|
||||
|
||||
下面是另一个例子。
|
||||
|
||||
@ -583,7 +583,7 @@ Promise.all([p1, p2])
|
||||
|
||||
## Promise.race()
|
||||
|
||||
`Promise.race`方法同样是将多个Promise实例,包装成一个新的Promise实例。
|
||||
`Promise.race`方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
|
||||
|
||||
```javascript
|
||||
const p = Promise.race([p1, p2, p3]);
|
||||
@ -593,7 +593,7 @@ const p = Promise.race([p1, p2, p3]);
|
||||
|
||||
`Promise.race`方法的参数与`Promise.all`方法一样,如果不是 Promise 实例,就会先调用下面讲到的`Promise.resolve`方法,将参数转为 Promise 实例,再进一步处理。
|
||||
|
||||
下面是一个例子,如果指定时间内没有获得结果,就将Promise的状态变为`reject`,否则变为`resolve`。
|
||||
下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为`reject`,否则变为`resolve`。
|
||||
|
||||
```javascript
|
||||
const p = Promise.race([
|
||||
@ -606,17 +606,17 @@ p.then(response => console.log(response));
|
||||
p.catch(error => console.log(error));
|
||||
```
|
||||
|
||||
上面代码中,如果5秒之内`fetch`方法无法返回结果,变量`p`的状态就会变为`rejected`,从而触发`catch`方法指定的回调函数。
|
||||
上面代码中,如果 5 秒之内`fetch`方法无法返回结果,变量`p`的状态就会变为`rejected`,从而触发`catch`方法指定的回调函数。
|
||||
|
||||
## Promise.resolve()
|
||||
|
||||
有时需要将现有对象转为Promise对象,`Promise.resolve`方法就起到这个作用。
|
||||
有时需要将现有对象转为 Promise 对象,`Promise.resolve`方法就起到这个作用。
|
||||
|
||||
```javascript
|
||||
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
|
||||
```
|
||||
|
||||
上面代码将jQuery生成的`deferred`对象,转为一个新的Promise对象。
|
||||
上面代码将 jQuery 生成的`deferred`对象,转为一个新的 Promise 对象。
|
||||
|
||||
`Promise.resolve`等价于下面的写法。
|
||||
|
||||
@ -628,9 +628,9 @@ new Promise(resolve => resolve('foo'))
|
||||
|
||||
`Promise.resolve`方法的参数分成四种情况。
|
||||
|
||||
**(1)参数是一个Promise实例**
|
||||
**(1)参数是一个 Promise 实例**
|
||||
|
||||
如果参数是Promise实例,那么`Promise.resolve`将不做任何修改、原封不动地返回这个实例。
|
||||
如果参数是 Promise 实例,那么`Promise.resolve`将不做任何修改、原封不动地返回这个实例。
|
||||
|
||||
**(2)参数是一个`thenable`对象**
|
||||
|
||||
@ -644,7 +644,7 @@ let thenable = {
|
||||
};
|
||||
```
|
||||
|
||||
`Promise.resolve`方法会将这个对象转为Promise对象,然后就立即执行`thenable`对象的`then`方法。
|
||||
`Promise.resolve`方法会将这个对象转为 Promise 对象,然后就立即执行`thenable`对象的`then`方法。
|
||||
|
||||
```javascript
|
||||
let thenable = {
|
||||
@ -659,11 +659,11 @@ p1.then(function(value) {
|
||||
});
|
||||
```
|
||||
|
||||
上面代码中,`thenable`对象的`then`方法执行后,对象`p1`的状态就变为`resolved`,从而立即执行最后那个`then`方法指定的回调函数,输出42。
|
||||
上面代码中,`thenable`对象的`then`方法执行后,对象`p1`的状态就变为`resolved`,从而立即执行最后那个`then`方法指定的回调函数,输出 42。
|
||||
|
||||
**(3)参数不是具有`then`方法的对象,或根本就不是对象**
|
||||
|
||||
如果参数是一个原始值,或者是一个不具有`then`方法的对象,则`Promise.resolve`方法返回一个新的Promise对象,状态为`resolved`。
|
||||
如果参数是一个原始值,或者是一个不具有`then`方法的对象,则`Promise.resolve`方法返回一个新的 Promise 对象,状态为`resolved`。
|
||||
|
||||
```javascript
|
||||
const p = Promise.resolve('Hello');
|
||||
@ -674,13 +674,13 @@ p.then(function (s){
|
||||
// Hello
|
||||
```
|
||||
|
||||
上面代码生成一个新的Promise对象的实例`p`。由于字符串`Hello`不属于异步操作(判断方法是字符串对象不具有then方法),返回Promise实例的状态从一生成就是`resolved`,所以回调函数会立即执行。`Promise.resolve`方法的参数,会同时传给回调函数。
|
||||
上面代码生成一个新的 Promise 对象的实例`p`。由于字符串`Hello`不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是`resolved`,所以回调函数会立即执行。`Promise.resolve`方法的参数,会同时传给回调函数。
|
||||
|
||||
**(4)不带有任何参数**
|
||||
|
||||
`Promise.resolve`方法允许调用时不带参数,直接返回一个`resolved`状态的Promise对象。
|
||||
`Promise.resolve`方法允许调用时不带参数,直接返回一个`resolved`状态的 Promise 对象。
|
||||
|
||||
所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用`Promise.resolve`方法。
|
||||
所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用`Promise.resolve`方法。
|
||||
|
||||
```javascript
|
||||
const p = Promise.resolve();
|
||||
@ -690,9 +690,9 @@ p.then(function () {
|
||||
});
|
||||
```
|
||||
|
||||
上面代码的变量`p`就是一个Promise对象。
|
||||
上面代码的变量`p`就是一个 Promise 对象。
|
||||
|
||||
需要注意的是,立即`resolve`的Promise对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
|
||||
需要注意的是,立即`resolve`的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
|
||||
|
||||
```javascript
|
||||
setTimeout(function () {
|
||||
@ -727,7 +727,7 @@ p.then(null, function (s) {
|
||||
// 出错了
|
||||
```
|
||||
|
||||
上面代码生成一个Promise对象的实例`p`,状态为`rejected`,回调函数会立即执行。
|
||||
上面代码生成一个 Promise 对象的实例`p`,状态为`rejected`,回调函数会立即执行。
|
||||
|
||||
注意,`Promise.reject()`方法的参数,会原封不动地作为`reject`的理由,变成后续方法的参数。这一点与`Promise.resolve`方法不一致。
|
||||
|
||||
@ -749,11 +749,11 @@ Promise.reject(thenable)
|
||||
|
||||
## 两个有用的附加方法
|
||||
|
||||
ES6的Promise API提供的方法不是很多,有些有用的方法可以自己部署。下面介绍如何部署两个不在ES6之中、但很有用的方法。
|
||||
ES6 的 Promise API 提供的方法不是很多,有些有用的方法可以自己部署。下面介绍如何部署两个不在 ES6 之中、但很有用的方法。
|
||||
|
||||
### done()
|
||||
|
||||
Promise对象的回调链,不管以`then`方法或`catch`方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个`done`方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
|
||||
Promise 对象的回调链,不管以`then`方法或`catch`方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise 内部的错误不会冒泡到全局)。因此,我们可以提供一个`done`方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
|
||||
|
||||
```javascript
|
||||
asyncFunc()
|
||||
@ -779,9 +779,9 @@ Promise.prototype.done = function (onFulfilled, onRejected) {
|
||||
|
||||
### finally()
|
||||
|
||||
`finally`方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与`done`方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
|
||||
`finally`方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。它与`done`方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
|
||||
|
||||
下面是一个例子,服务器使用Promise处理请求,然后使用`finally`方法关掉服务器。
|
||||
下面是一个例子,服务器使用 Promise 处理请求,然后使用`finally`方法关掉服务器。
|
||||
|
||||
```javascript
|
||||
server.listen(0)
|
||||
@ -803,7 +803,7 @@ Promise.prototype.finally = function (callback) {
|
||||
};
|
||||
```
|
||||
|
||||
上面代码中,不管前面的Promise是`fulfilled`还是`rejected`,都会执行回调函数`callback`。
|
||||
上面代码中,不管前面的 Promise 是`fulfilled`还是`rejected`,都会执行回调函数`callback`。
|
||||
|
||||
## 应用
|
||||
|
||||
@ -822,9 +822,9 @@ const preloadImage = function (path) {
|
||||
};
|
||||
```
|
||||
|
||||
### Generator函数与Promise的结合
|
||||
### Generator 函数与 Promise 的结合
|
||||
|
||||
使用Generator函数管理流程,遇到异步操作的时候,通常返回一个`Promise`对象。
|
||||
使用 Generator 函数管理流程,遇到异步操作的时候,通常返回一个`Promise`对象。
|
||||
|
||||
```javascript
|
||||
function getFoo () {
|
||||
@ -861,7 +861,7 @@ function run (generator) {
|
||||
run(g);
|
||||
```
|
||||
|
||||
上面代码的Generator函数`g`之中,有一个异步操作`getFoo`,它返回的就是一个`Promise`对象。函数`run`用来处理这个`Promise`对象,并调用下一个`next`方法。
|
||||
上面代码的 Generator 函数`g`之中,有一个异步操作`getFoo`,它返回的就是一个`Promise`对象。函数`run`用来处理这个`Promise`对象,并调用下一个`next`方法。
|
||||
|
||||
## Promise.try()
|
||||
|
||||
@ -976,4 +976,3 @@ Promise.try(database.users.get({id: userId}))
|
||||
```
|
||||
|
||||
事实上,`Promise.try`就是模拟`try`代码块,就像`promise.catch`模拟的是`catch`代码块。
|
||||
|
||||
|
@ -123,7 +123,7 @@ fproxy.foo === "Hello, foo" // true
|
||||
|
||||
对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
|
||||
|
||||
下面是 Proxy 支持的拦截操作一览,一共13种。
|
||||
下面是 Proxy 支持的拦截操作一览,一共 13 种。
|
||||
|
||||
- **get(target, propKey, receiver)**:拦截对象属性的读取,比如`proxy.foo`和`proxy['foo']`。
|
||||
- **set(target, propKey, value, receiver)**:拦截对象属性的设置,比如`proxy.foo = v`或`proxy['foo'] = v`,返回一个布尔值。
|
||||
@ -317,7 +317,7 @@ proxy.foo
|
||||
|
||||
`set`方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
|
||||
|
||||
假定`Person`对象有一个`age`属性,该属性应该是一个不大于200的整数,那么可以使用`Proxy`保证`age`的属性值符合要求。
|
||||
假定`Person`对象有一个`age`属性,该属性应该是一个不大于 200 的整数,那么可以使用`Proxy`保证`age`的属性值符合要求。
|
||||
|
||||
```javascript
|
||||
let validator = {
|
||||
@ -1049,4 +1049,3 @@ function createWebService(baseUrl) {
|
||||
```
|
||||
|
||||
同理,Proxy 也可以用来实现数据库的 ORM 层。
|
||||
|
||||
|
@ -2,46 +2,46 @@
|
||||
|
||||
## 官方文件
|
||||
|
||||
- [ECMAScript® 2015 Language Specification](http://www.ecma-international.org/ecma-262/6.0/index.html): ECMAScript 2015规格
|
||||
- [ECMAScript® 2016 Language Specification](http://www.ecma-international.org/ecma-262/7.0/): ECMAScript 2016规格
|
||||
- [ECMAScript® 2017 Language Specification](https://tc39.github.io/ecma262/):ECMAScript 2017规格(草案)
|
||||
- [ECMAScript Current Proposals](https://github.com/tc39/ecma262): ECMAScript当前的所有提案
|
||||
- [ECMAScript® 2015 Language Specification](http://www.ecma-international.org/ecma-262/6.0/index.html): ECMAScript 2015 规格
|
||||
- [ECMAScript® 2016 Language Specification](http://www.ecma-international.org/ecma-262/7.0/): ECMAScript 2016 规格
|
||||
- [ECMAScript® 2017 Language Specification](https://tc39.github.io/ecma262/):ECMAScript 2017 规格(草案)
|
||||
- [ECMAScript Current Proposals](https://github.com/tc39/ecma262): ECMAScript 当前的所有提案
|
||||
- [ECMAScript Active Proposals](https://github.com/tc39/proposals): 已经进入正式流程的提案
|
||||
- [ECMAscript proposals](https://github.com/hemanth/es-next):从阶段0到阶段4的所有提案列表
|
||||
- [ECMAscript proposals](https://github.com/hemanth/es-next):从阶段 0 到阶段 4 的所有提案列表
|
||||
- [TC39 meeting agendas](https://github.com/tc39/agendas): TC39 委员会历年的会议记录
|
||||
- [ECMAScript Daily](https://ecmascript-daily.github.io/): TC39委员会的动态
|
||||
- [ECMAScript Daily](https://ecmascript-daily.github.io/): TC39 委员会的动态
|
||||
- [The TC39 Process](https://tc39.github.io/process-document/): 提案进入正式规格的流程
|
||||
- [TC39: A Process Sketch, Stages 0 and 1](https://thefeedbackloop.xyz/tc39-a-process-sketch-stages-0-and-1/): Stage 0 和 Stage 1 的含义
|
||||
- [TC39 Process Sketch, Stage 2](https://thefeedbackloop.xyz/tc39-process-sketch-stage-2/): Stage 2 的含义
|
||||
|
||||
## 综合介绍
|
||||
|
||||
- Axel Rauschmayer, [Exploring ES6: Upgrade to the next version of JavaScript](http://exploringjs.com/es6/): ES6的专著,本书的许多代码实例来自该书
|
||||
- Axel Rauschmayer, [Exploring ES6: Upgrade to the next version of JavaScript](http://exploringjs.com/es6/): ES6 的专著,本书的许多代码实例来自该书
|
||||
- Sayanee Basu, [Use ECMAScript 6 Today](http://net.tutsplus.com/articles/news/ecmascript-6-today/)
|
||||
- Ariya Hidayat, [Toward Modern Web Apps with ECMAScript 6](http://www.sencha.com/blog/toward-modern-web-apps-with-ecmascript-6/)
|
||||
- Dale Schouten, [10 Ecmascript-6 tricks you can perform right now](http://html5hub.com/10-ecmascript-6-tricks-you-can-perform-right-now/)
|
||||
- Colin Toh, [Lightweight ES6 Features That Pack A Punch](http://colintoh.com/blog/lightweight-es6-features): ES6的一些“轻量级”的特性介绍
|
||||
- Colin Toh, [Lightweight ES6 Features That Pack A Punch](http://colintoh.com/blog/lightweight-es6-features): ES6 的一些“轻量级”的特性介绍
|
||||
- Domenic Denicola, [ES6: The Awesome Parts](http://www.slideshare.net/domenicdenicola/es6-the-awesome-parts)
|
||||
- Nicholas C. Zakas, [Understanding ECMAScript 6](https://github.com/nzakas/understandinges6)
|
||||
- Justin Drake, [ECMAScript 6 in Node.JS](https://github.com/JustinDrake/node-es6-examples)
|
||||
- Ryan Dao, [Summary of ECMAScript 6 major features](http://ryandao.net/portal/content/summary-ecmascript-6-major-features)
|
||||
- Luke Hoban, [ES6 features](https://github.com/lukehoban/es6features): ES6新语法点的罗列
|
||||
- Traceur-compiler, [Language Features](https://github.com/google/traceur-compiler/wiki/LanguageFeatures): Traceur文档列出的一些ES6例子
|
||||
- Axel Rauschmayer, [ECMAScript 6: what’s next for JavaScript?](https://speakerdeck.com/rauschma/ecmascript-6-whats-next-for-javascript-august-2014): 关于ES6新增语法的综合介绍,有很多例子
|
||||
- Axel Rauschmayer, [Getting started with ECMAScript 6](http://www.2ality.com/2015/08/getting-started-es6.html): ES6语法点的综合介绍
|
||||
- Luke Hoban, [ES6 features](https://github.com/lukehoban/es6features): ES6 新语法点的罗列
|
||||
- Traceur-compiler, [Language Features](https://github.com/google/traceur-compiler/wiki/LanguageFeatures): Traceur 文档列出的一些 ES6 例子
|
||||
- Axel Rauschmayer, [ECMAScript 6: what’s next for JavaScript?](https://speakerdeck.com/rauschma/ecmascript-6-whats-next-for-javascript-august-2014): 关于 ES6 新增语法的综合介绍,有很多例子
|
||||
- Axel Rauschmayer, [Getting started with ECMAScript 6](http://www.2ality.com/2015/08/getting-started-es6.html): ES6 语法点的综合介绍
|
||||
- Toby Ho, [ES6 in io.js](http://davidwalsh.name/es6-io)
|
||||
- Guillermo Rauch, [ECMAScript 6](http://rauchg.com/2015/ecmascript-6/)
|
||||
- Charles King, [The power of ECMAScript 6](http://charlesbking.com/power_of_es6/#/)
|
||||
- Benjamin De Cock, [Frontend Guidelines](https://github.com/bendc/frontend-guidelines): ES6最佳实践
|
||||
- Benjamin De Cock, [Frontend Guidelines](https://github.com/bendc/frontend-guidelines): ES6 最佳实践
|
||||
- Jani Hartikainen, [ES6: What are the benefits of the new features in practice?](http://codeutopia.net/blog/2015/01/06/es6-what-are-the-benefits-of-the-new-features-in-practice/)
|
||||
- kangax, [Javascript quiz. ES6 edition](http://perfectionkills.com/javascript-quiz-es6/): ES6小测试
|
||||
- Jeremy Fairbank, [HTML5DevConf ES7 and Beyond!](https://speakerdeck.com/jfairbank/html5devconf-es7-and-beyond): ES7新增语法点介绍
|
||||
- kangax, [Javascript quiz. ES6 edition](http://perfectionkills.com/javascript-quiz-es6/): ES6 小测试
|
||||
- Jeremy Fairbank, [HTML5DevConf ES7 and Beyond!](https://speakerdeck.com/jfairbank/html5devconf-es7-and-beyond): ES7 新增语法点介绍
|
||||
|
||||
## let 和 const
|
||||
|
||||
- Kyle Simpson, [For and against let](http://davidwalsh.name/for-and-against-let): 讨论let命令的作用域
|
||||
- kangax, [Why typeof is no longer “safe”](http://es-discourse.com/t/why-typeof-is-no-longer-safe/15): 讨论在块级作用域内,let命令的变量声明和赋值的行为
|
||||
- Axel Rauschmayer, [Variables and scoping in ECMAScript 6](http://www.2ality.com/2015/02/es6-scoping.html): 讨论块级作用域与let和const的行为
|
||||
- Kyle Simpson, [For and against let](http://davidwalsh.name/for-and-against-let): 讨论 let 命令的作用域
|
||||
- kangax, [Why typeof is no longer “safe”](http://es-discourse.com/t/why-typeof-is-no-longer-safe/15): 讨论在块级作用域内,let 命令的变量声明和赋值的行为
|
||||
- Axel Rauschmayer, [Variables and scoping in ECMAScript 6](http://www.2ality.com/2015/02/es6-scoping.html): 讨论块级作用域与 let 和 const 的行为
|
||||
- Nicolas Bevacqua, [ES6 Let, Const and the “Temporal Dead Zone” (TDZ) in Depth](http://ponyfoo.com/articles/es6-let-const-and-temporal-dead-zone-in-depth)
|
||||
- acorn, [Function statements in strict mode](https://github.com/ternjs/acorn/issues/118): 块级作用域对严格模式的函数声明的影响
|
||||
- Axel Rauschmayer, [ES proposal: global](http://www.2ality.com/2016/09/global.html): 顶层对象`global`
|
||||
@ -62,8 +62,8 @@
|
||||
|
||||
## 正则
|
||||
|
||||
- Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的u修饰符
|
||||
- Axel Rauschmayer, [New regular expression features in ECMAScript 6](http://www.2ality.com/2015/07/regexp-es6.html):ES6正则特性的详细介绍
|
||||
- Mathias Bynens, [Unicode-aware regular expressions in ES6](https://mathiasbynens.be/notes/es6-unicode-regex): 详细介绍正则表达式的 u 修饰符
|
||||
- Axel Rauschmayer, [New regular expression features in ECMAScript 6](http://www.2ality.com/2015/07/regexp-es6.html):ES6 正则特性的详细介绍
|
||||
- Yang Guo, [RegExp lookbehind assertions](http://v8project.blogspot.jp/2016/02/regexp-lookbehind-assertions.html):介绍后行断言
|
||||
- Axel Rauschmayer, [ES proposal: RegExp named capture groups](http://2ality.com/2017/05/regexp-named-capture-groups.html): 具名组匹配的介绍
|
||||
|
||||
@ -74,8 +74,8 @@
|
||||
|
||||
## 数组
|
||||
|
||||
- Axel Rauschmayer, [ECMAScript 6’s new array methods](http://www.2ality.com/2014/05/es6-array-methods.html): 对ES6新增的数组方法的全面介绍
|
||||
- TC39, [Array.prototype.includes](https://github.com/tc39/Array.prototype.includes/): 数组的includes方法的规格
|
||||
- Axel Rauschmayer, [ECMAScript 6’s new array methods](http://www.2ality.com/2014/05/es6-array-methods.html): 对 ES6 新增的数组方法的全面介绍
|
||||
- TC39, [Array.prototype.includes](https://github.com/tc39/Array.prototype.includes/): 数组的 includes 方法的规格
|
||||
- Axel Rauschmayer, [ECMAScript 6: holes in Arrays](http://www.2ality.com/2015/09/holes-arrays-es6.html): 数组的空位问题
|
||||
|
||||
## 函数
|
||||
@ -84,18 +84,18 @@
|
||||
- Jack Franklin, [Real Life ES6 - Arrow Functions](http://javascriptplayground.com/blog/2014/04/real-life-es6-arrow-fn/)
|
||||
- Axel Rauschmayer, [Handling required parameters in ECMAScript 6](http://www.2ality.com/2014/04/required-parameters-es6.html)
|
||||
- Dmitry Soshnikov, [ES6 Notes: Default values of parameters](http://dmitrysoshnikov.com/ecmascript/es6-notes-default-values-of-parameters/): 介绍参数的默认值
|
||||
- Ragan Wald, [Destructuring and Recursion in ES6](http://raganwald.com/2015/02/02/destructuring.html): rest参数和扩展运算符的详细介绍
|
||||
- Axel Rauschmayer, [The names of functions in ES6](http://www.2ality.com/2015/09/function-names-es6.html): 函数的name属性的详细介绍
|
||||
- Kyle Simpson, [Arrow This](http://blog.getify.com/arrow-this/): 箭头函数并没有自己的this
|
||||
- Derick Bailey, [Do ES6 Arrow Functions Really Solve “this” In JavaScript?](http://derickbailey.com/2015/09/28/do-es6-arrow-functions-really-solve-this-in-javascript/):使用箭头函数处理this指向,必须非常小心
|
||||
- Ragan Wald, [Destructuring and Recursion in ES6](http://raganwald.com/2015/02/02/destructuring.html): rest 参数和扩展运算符的详细介绍
|
||||
- Axel Rauschmayer, [The names of functions in ES6](http://www.2ality.com/2015/09/function-names-es6.html): 函数的 name 属性的详细介绍
|
||||
- Kyle Simpson, [Arrow This](http://blog.getify.com/arrow-this/): 箭头函数并没有自己的 this
|
||||
- Derick Bailey, [Do ES6 Arrow Functions Really Solve “this” In JavaScript?](http://derickbailey.com/2015/09/28/do-es6-arrow-functions-really-solve-this-in-javascript/):使用箭头函数处理 this 指向,必须非常小心
|
||||
- Mark McDonnell, [Understanding recursion in functional JavaScript programming](http://www.integralist.co.uk/posts/js-recursion.html): 如何自己实现尾递归优化
|
||||
- Nicholas C. Zakas, [The ECMAScript 2016 change you probably don't know](https://www.nczonline.net/blog/2016/10/the-ecmascript-2016-change-you-probably-dont-know/): 使用参数默认值时,不能在函数内部显式开启严格模式
|
||||
- Axel Rauschmayer, [ES proposal: optional catch binding](http://2ality.com/2017/08/optional-catch-binding.html)
|
||||
|
||||
## 对象
|
||||
|
||||
- Addy Osmani, [Data-binding Revolutions with Object.observe()](http://www.html5rocks.com/en/tutorials/es7/observe/): 介绍Object.observe()的概念
|
||||
- Sella Rafaeli, [Native JavaScript Data-Binding](http://www.sellarafaeli.com/blog/native_javascript_data_binding): 如何使用Object.observe方法,实现数据对象与DOM对象的双向绑定
|
||||
- Addy Osmani, [Data-binding Revolutions with Object.observe()](http://www.html5rocks.com/en/tutorials/es7/observe/): 介绍 Object.observe()的概念
|
||||
- Sella Rafaeli, [Native JavaScript Data-Binding](http://www.sellarafaeli.com/blog/native_javascript_data_binding): 如何使用 Object.observe 方法,实现数据对象与 DOM 对象的双向绑定
|
||||
- Axel Rauschmayer, [`__proto__` in ECMAScript 6](http://www.2ality.com/2015/09/proto-es6.html)
|
||||
- Axel Rauschmayer, [Enumerability in ECMAScript 6](http://www.2ality.com/2015/10/enumerability-es6.html)
|
||||
- Axel Rauschmayer, [ES proposal: Object.getOwnPropertyDescriptors()](http://www.2ality.com/2016/02/object-getownpropertydescriptors.html)
|
||||
@ -103,29 +103,29 @@
|
||||
|
||||
## Symbol
|
||||
|
||||
- Axel Rauschmayer, [Symbols in ECMAScript 6](http://www.2ality.com/2014/12/es6-symbols.html): Symbol简介
|
||||
- MDN, [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol): Symbol类型的详细介绍
|
||||
- Axel Rauschmayer, [Symbols in ECMAScript 6](http://www.2ality.com/2014/12/es6-symbols.html): Symbol 简介
|
||||
- MDN, [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol): Symbol 类型的详细介绍
|
||||
- Jason Orendorff, [ES6 In Depth: Symbols](https://hacks.mozilla.org/2015/06/es6-in-depth-symbols/)
|
||||
- Keith Cirkel, [Metaprogramming in ES6: Symbols and why they're awesome](http://blog.keithcirkel.co.uk/metaprogramming-in-es6-symbols/): Symbol的深入介绍
|
||||
- Keith Cirkel, [Metaprogramming in ES6: Symbols and why they're awesome](http://blog.keithcirkel.co.uk/metaprogramming-in-es6-symbols/): Symbol 的深入介绍
|
||||
- Axel Rauschmayer, [Customizing ES6 via well-known symbols](http://www.2ality.com/2015/09/well-known-symbols-es6.html)
|
||||
- Derick Bailey, [Creating A True Singleton In Node.js, With ES6 Symbols](https://derickbailey.com/2016/03/09/creating-a-true-singleton-in-node-js-with-es6-symbols/)
|
||||
- Das Surma, [How to read web specs Part IIa – Or: ECMAScript Symbols](https://dassur.ma/things/reading-specs-2/): 介绍 Symbol 的规格
|
||||
|
||||
## Set和Map
|
||||
## Set 和 Map
|
||||
|
||||
- Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍WeakSet数据结构
|
||||
- Dwayne Charrington, [What Are Weakmaps In ES6?](http://ilikekillnerds.com/2015/02/what-are-weakmaps-in-es6/): WeakMap数据结构介绍
|
||||
- Axel Rauschmayer, [ECMAScript 6: maps and sets](http://www.2ality.com/2015/01/es6-maps-sets.html): Set和Map结构的详细介绍
|
||||
- Jason Orendorff, [ES6 In Depth: Collections](https://hacks.mozilla.org/2015/06/es6-in-depth-collections/):Set和Map结构的设计思想
|
||||
- Axel Rauschmayer, [Converting ES6 Maps to and from JSON](http://www.2ality.com/2015/08/es6-map-json.html): 如何将Map与其他数据结构互相转换
|
||||
- Mozilla Developer Network, [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet):介绍 WeakSet 数据结构
|
||||
- Dwayne Charrington, [What Are Weakmaps In ES6?](http://ilikekillnerds.com/2015/02/what-are-weakmaps-in-es6/): WeakMap 数据结构介绍
|
||||
- Axel Rauschmayer, [ECMAScript 6: maps and sets](http://www.2ality.com/2015/01/es6-maps-sets.html): Set 和 Map 结构的详细介绍
|
||||
- Jason Orendorff, [ES6 In Depth: Collections](https://hacks.mozilla.org/2015/06/es6-in-depth-collections/):Set 和 Map 结构的设计思想
|
||||
- Axel Rauschmayer, [Converting ES6 Maps to and from JSON](http://www.2ality.com/2015/08/es6-map-json.html): 如何将 Map 与其他数据结构互相转换
|
||||
|
||||
## Proxy 和 Reflect
|
||||
|
||||
- Nicholas C. Zakas, [Creating defensive objects with ES6 proxies](http://www.nczonline.net/blog/2014/04/22/creating-defensive-objects-with-es6-proxies/)
|
||||
- Axel Rauschmayer, [Meta programming with ECMAScript 6 proxies](http://www.2ality.com/2014/12/es6-proxies.html): Proxy详解
|
||||
- Daniel Zautner, [Meta-programming JavaScript Using Proxies](http://dzautner.com/meta-programming-javascript-using-proxies/): 使用Proxy实现元编程
|
||||
- Tom Van Cutsem, [Harmony-reflect](https://github.com/tvcutsem/harmony-reflect/wiki): Reflect对象的设计目的
|
||||
- Tom Van Cutsem, [Proxy Traps](https://github.com/tvcutsem/harmony-reflect/blob/master/doc/traps.md): Proxy拦截操作一览
|
||||
- Axel Rauschmayer, [Meta programming with ECMAScript 6 proxies](http://www.2ality.com/2014/12/es6-proxies.html): Proxy 详解
|
||||
- Daniel Zautner, [Meta-programming JavaScript Using Proxies](http://dzautner.com/meta-programming-javascript-using-proxies/): 使用 Proxy 实现元编程
|
||||
- Tom Van Cutsem, [Harmony-reflect](https://github.com/tvcutsem/harmony-reflect/wiki): Reflect 对象的设计目的
|
||||
- Tom Van Cutsem, [Proxy Traps](https://github.com/tvcutsem/harmony-reflect/blob/master/doc/traps.md): Proxy 拦截操作一览
|
||||
- Tom Van Cutsem, [Reflect API](https://github.com/tvcutsem/harmony-reflect/blob/master/doc/api.md)
|
||||
- Tom Van Cutsem, [Proxy Handler API](https://github.com/tvcutsem/harmony-reflect/blob/master/doc/handler_api.md)
|
||||
- Nicolas Bevacqua, [ES6 Proxies in Depth](http://ponyfoo.com/articles/es6-proxies-in-depth)
|
||||
@ -139,9 +139,9 @@
|
||||
|
||||
- Jake Archibald, [JavaScript Promises: There and back again](http://www.html5rocks.com/en/tutorials/es6/promises/)
|
||||
- Tilde, [rsvp.js](https://github.com/tildeio/rsvp.js)
|
||||
- Sandeep Panda, [An Overview of JavaScript Promises](http://www.sitepoint.com/overview-javascript-promises/): ES6 Promise入门介绍
|
||||
- Dave Atchley, [ES6 Promises](http://www.datchley.name/es6-promises/): Promise的语法介绍
|
||||
- Axel Rauschmayer, [ECMAScript 6 promises (2/2): the API](http://www.2ality.com/2014/10/es6-promises-api.html): 对ES6 Promise规格和用法的详细介绍
|
||||
- Sandeep Panda, [An Overview of JavaScript Promises](http://www.sitepoint.com/overview-javascript-promises/): ES6 Promise 入门介绍
|
||||
- Dave Atchley, [ES6 Promises](http://www.datchley.name/es6-promises/): Promise 的语法介绍
|
||||
- Axel Rauschmayer, [ECMAScript 6 promises (2/2): the API](http://www.2ality.com/2014/10/es6-promises-api.html): 对 ES6 Promise 规格和用法的详细介绍
|
||||
- Jack Franklin, [Embracing Promises in JavaScript](http://javascriptplayground.com/blog/2015/02/promises/): catch 方法的例子
|
||||
- Ronald Chen, [How to escape Promise Hell](https://medium.com/@pyrolistical/how-to-get-out-of-promise-hell-8c20e0ab0513#.2an1he6vf): 如何使用`Promise.all`方法的一些很好的例子
|
||||
- Jordan Harband, [proposal-promise-try](https://github.com/ljharb/proposal-promise-try): Promise.try() 方法的提案
|
||||
@ -152,9 +152,9 @@
|
||||
|
||||
- Mozilla Developer Network, [Iterators and generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators)
|
||||
- Mozilla Developer Network, [The Iterator protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/The_Iterator_protocol)
|
||||
- Jason Orendorff, [ES6 In Depth: Iterators and the for-of loop](https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/): 遍历器与for...of循环的介绍
|
||||
- Axel Rauschmayer, [Iterators and generators in ECMAScript 6](http://www.2ality.com/2013/06/iterators-generators.html): 探讨Iterator和Generator的设计目的
|
||||
- Axel Rauschmayer, [Iterables and iterators in ECMAScript 6](http://www.2ality.com/2015/02/es6-iteration.html): Iterator的详细介绍
|
||||
- Jason Orendorff, [ES6 In Depth: Iterators and the for-of loop](https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/): 遍历器与 for...of 循环的介绍
|
||||
- Axel Rauschmayer, [Iterators and generators in ECMAScript 6](http://www.2ality.com/2013/06/iterators-generators.html): 探讨 Iterator 和 Generator 的设计目的
|
||||
- Axel Rauschmayer, [Iterables and iterators in ECMAScript 6](http://www.2ality.com/2015/02/es6-iteration.html): Iterator 的详细介绍
|
||||
- Kyle Simpson, [Iterating ES6 Numbers](http://blog.getify.com/iterating-es6-numbers/): 在数值对象上部署遍历器
|
||||
|
||||
## Generator
|
||||
@ -162,55 +162,55 @@
|
||||
- Matt Baker, [Replacing callbacks with ES6 Generators](http://flippinawesome.org/2014/02/10/replacing-callbacks-with-es6-generators/)
|
||||
- Steven Sanderson, [Experiments with Koa and JavaScript Generators](http://blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators/)
|
||||
- jmar777, [What's the Big Deal with Generators?](http://devsmash.com/blog/whats-the-big-deal-with-generators)
|
||||
- Marc Harter, [Generators in Node.js: Common Misconceptions and Three Good Use Cases](http://strongloop.com/strongblog/how-to-generators-node-js-yield-use-cases/): 讨论Generator函数的作用
|
||||
- StackOverflow, [ES6 yield : what happens to the arguments of the first call next()?](http://stackoverflow.com/questions/20977379/es6-yield-what-happens-to-the-arguments-of-the-first-call-next): 第一次使用next方法时不能带有参数
|
||||
- Kyle Simpson, [ES6 Generators: Complete Series](http://davidwalsh.name/es6-generators): 由浅入深探讨Generator的系列文章,共四篇
|
||||
- Gajus Kuizinas, [The Definitive Guide to the JavaScript Generators](http://gajus.com/blog/2/the-definetive-guide-to-the-javascript-generators): 对Generator的综合介绍
|
||||
- Jan Krems, [Generators Are Like Arrays](https://gist.github.com/jkrems/04a2b34fb9893e4c2b5c): 讨论Generator可以被当作数据结构看待
|
||||
- Harold Cooper, [Coroutine Event Loops in Javascript](http://syzygy.st/javascript-coroutines/): Generator用于实现状态机
|
||||
- Ruslan Ismagilov, [learn-generators](https://github.com/isRuslan/learn-generators): 编程练习,共6道题
|
||||
- Steven Sanderson, [Experiments with Koa and JavaScript Generators](http://blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators/): Generator入门介绍,以Koa框架为例
|
||||
- Mahdi Dibaiee, [ES7 Array and Generator comprehensions](http://dibaiee.ir/es7-array-generator-comprehensions/):ES7的Generator推导
|
||||
- Marc Harter, [Generators in Node.js: Common Misconceptions and Three Good Use Cases](http://strongloop.com/strongblog/how-to-generators-node-js-yield-use-cases/): 讨论 Generator 函数的作用
|
||||
- StackOverflow, [ES6 yield : what happens to the arguments of the first call next()?](http://stackoverflow.com/questions/20977379/es6-yield-what-happens-to-the-arguments-of-the-first-call-next): 第一次使用 next 方法时不能带有参数
|
||||
- Kyle Simpson, [ES6 Generators: Complete Series](http://davidwalsh.name/es6-generators): 由浅入深探讨 Generator 的系列文章,共四篇
|
||||
- Gajus Kuizinas, [The Definitive Guide to the JavaScript Generators](http://gajus.com/blog/2/the-definetive-guide-to-the-javascript-generators): 对 Generator 的综合介绍
|
||||
- Jan Krems, [Generators Are Like Arrays](https://gist.github.com/jkrems/04a2b34fb9893e4c2b5c): 讨论 Generator 可以被当作数据结构看待
|
||||
- Harold Cooper, [Coroutine Event Loops in Javascript](http://syzygy.st/javascript-coroutines/): Generator 用于实现状态机
|
||||
- Ruslan Ismagilov, [learn-generators](https://github.com/isRuslan/learn-generators): 编程练习,共 6 道题
|
||||
- Steven Sanderson, [Experiments with Koa and JavaScript Generators](http://blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators/): Generator 入门介绍,以 Koa 框架为例
|
||||
- Mahdi Dibaiee, [ES7 Array and Generator comprehensions](http://dibaiee.ir/es7-array-generator-comprehensions/):ES7 的 Generator 推导
|
||||
- Nicolas Bevacqua, [ES6 Generators in Depth](http://ponyfoo.com/articles/es6-generators-in-depth)
|
||||
- Axel Rauschmayer, [ES6 generators in depth](http://www.2ality.com/2015/03/es6-generators.html): Generator规格的详尽讲解
|
||||
- Axel Rauschmayer, [ES6 generators in depth](http://www.2ality.com/2015/03/es6-generators.html): Generator 规格的详尽讲解
|
||||
- Derick Bailey, [Using ES6 Generators To Short-Circuit Hierarchical Data Iteration](https://derickbailey.com/2015/10/05/using-es6-generators-to-short-circuit-hierarchical-data-iteration/):使用 for...of 循环完成预定的操作步骤
|
||||
|
||||
## 异步操作和Async函数
|
||||
## 异步操作和 Async 函数
|
||||
|
||||
- Luke Hoban, [Async Functions for ECMAScript](https://github.com/lukehoban/ecmascript-asyncawait): Async函数的设计思想,与Promise、Gernerator函数的关系
|
||||
- Jafar Husain, [Asynchronous Generators for ES7](https://github.com/jhusain/asyncgenerator): Async函数的深入讨论
|
||||
- Nolan Lawson, [Taming the asynchronous beast with ES7](http://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html): async函数通俗的实例讲解
|
||||
- Jafar Husain, [Async Generators](https://docs.google.com/file/d/0B4PVbLpUIdzoMDR5dWstRllXblU/view?sle=true): 对async与Generator混合使用的一些讨论
|
||||
- Daniel Brain, [Understand promises before you start using async/await](https://medium.com/@bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8): 讨论async/await与Promise的关系
|
||||
- Luke Hoban, [Async Functions for ECMAScript](https://github.com/lukehoban/ecmascript-asyncawait): Async 函数的设计思想,与 Promise、Gernerator 函数的关系
|
||||
- Jafar Husain, [Asynchronous Generators for ES7](https://github.com/jhusain/asyncgenerator): Async 函数的深入讨论
|
||||
- Nolan Lawson, [Taming the asynchronous beast with ES7](http://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html): async 函数通俗的实例讲解
|
||||
- Jafar Husain, [Async Generators](https://docs.google.com/file/d/0B4PVbLpUIdzoMDR5dWstRllXblU/view?sle=true): 对 async 与 Generator 混合使用的一些讨论
|
||||
- Daniel Brain, [Understand promises before you start using async/await](https://medium.com/@bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8): 讨论 async/await 与 Promise 的关系
|
||||
- Jake Archibald, [Async functions - making promises friendly](https://developers.google.com/web/fundamentals/getting-started/primers/async-functions)
|
||||
- Axel Rauschmayer, [ES proposal: asynchronous iteration](http://www.2ality.com/2016/10/asynchronous-iteration.html): 异步遍历器的详细介绍
|
||||
- Dima Grossman, [How to write async await without try-catch blocks in Javascript](http://blog.grossman.io/how-to-write-async-await-without-try-catch-blocks-in-javascript/): 除了 try/catch 以外的 async 函数内部捕捉错误的方法
|
||||
|
||||
## Class
|
||||
|
||||
- Sebastian Porto, [ES6 classes and JavaScript prototypes](https://reinteractive.net/posts/235-es6-classes-and-javascript-prototypes): ES6 Class的写法与ES5 Prototype的写法对比
|
||||
- Jack Franklin, [An introduction to ES6 classes](http://javascriptplayground.com/blog/2014/07/introduction-to-es6-classes-tutorial/): ES6 class的入门介绍
|
||||
- Sebastian Porto, [ES6 classes and JavaScript prototypes](https://reinteractive.net/posts/235-es6-classes-and-javascript-prototypes): ES6 Class 的写法与 ES5 Prototype 的写法对比
|
||||
- Jack Franklin, [An introduction to ES6 classes](http://javascriptplayground.com/blog/2014/07/introduction-to-es6-classes-tutorial/): ES6 class 的入门介绍
|
||||
- Axel Rauschmayer, [ECMAScript 6: new OOP features besides classes](http://www.2ality.com/2014/12/es6-oop.html)
|
||||
- Axel Rauschmayer, [Classes in ECMAScript 6 (final semantics)](http://www.2ality.com/2015/02/es6-classes-final.html): Class语法的详细介绍和设计思想分析
|
||||
- Eric Faust, [ES6 In Depth: Subclassing](https://hacks.mozilla.org/2015/08/es6-in-depth-subclassing/): Class语法的深入介绍
|
||||
- Nicolás Bevacqua, [Binding Methods to Class Instance Objects](https://ponyfoo.com/articles/binding-methods-to-class-instance-objects): 如何绑定类的实例中的this
|
||||
- Axel Rauschmayer, [Classes in ECMAScript 6 (final semantics)](http://www.2ality.com/2015/02/es6-classes-final.html): Class 语法的详细介绍和设计思想分析
|
||||
- Eric Faust, [ES6 In Depth: Subclassing](https://hacks.mozilla.org/2015/08/es6-in-depth-subclassing/): Class 语法的深入介绍
|
||||
- Nicolás Bevacqua, [Binding Methods to Class Instance Objects](https://ponyfoo.com/articles/binding-methods-to-class-instance-objects): 如何绑定类的实例中的 this
|
||||
|
||||
## Decorator
|
||||
|
||||
- Maximiliano Fierro, [Declarative vs Imperative](http://elmasse.github.io/js/decorators-bindings-es7.html): Decorators和Mixin介绍
|
||||
- Justin Fagnani, ["Real" Mixins with JavaScript Classes](http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/): 使用类的继承实现Mixin
|
||||
- Addy Osmani, [Exploring ES2016 Decorators](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841): Decorator的深入介绍
|
||||
- Maximiliano Fierro, [Declarative vs Imperative](http://elmasse.github.io/js/decorators-bindings-es7.html): Decorators 和 Mixin 介绍
|
||||
- Justin Fagnani, ["Real" Mixins with JavaScript Classes](http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/): 使用类的继承实现 Mixin
|
||||
- Addy Osmani, [Exploring ES2016 Decorators](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841): Decorator 的深入介绍
|
||||
- Sebastian McKenzie, [Allow decorators for functions as well](https://github.com/wycats/javascript-decorators/issues/4): 为什么修饰器不能用于函数
|
||||
- Maximiliano Fierro, [Traits with ES7 Decorators](http://cocktailjs.github.io/blog/traits-with-es7-decorators.html): Trait的用法介绍
|
||||
- Maximiliano Fierro, [Traits with ES7 Decorators](http://cocktailjs.github.io/blog/traits-with-es7-decorators.html): Trait 的用法介绍
|
||||
- Jonathan Creamer: [Using ES2016 Decorators to Publish on an Event Bus](http://jonathancreamer.com/using-es2016-decorators-to-publish-on-an-event-bus/): 使用修饰器实现自动发布事件
|
||||
|
||||
## Module
|
||||
|
||||
- Jack Franklin, [JavaScript Modules the ES6 Way](http://24ways.org/2014/javascript-modules-the-es6-way/): ES6模块入门
|
||||
- Axel Rauschmayer, [ECMAScript 6 modules: the final syntax](http://www.2ality.com/2014/09/es6-modules-final.html): ES6模块的介绍,以及与CommonJS规格的详细比较
|
||||
- Dave Herman, [Static module resolution](http://calculist.org/blog/2012/06/29/static-module-resolution/): ES6模块的静态化设计思想
|
||||
- Jason Orendorff, [ES6 In Depth: Modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/): ES6模块设计思想的介绍
|
||||
- Ben Newman, [The Importance of import and export](http://benjamn.github.io/empirenode-2015/#/): ES6模块的设计思想
|
||||
- Jack Franklin, [JavaScript Modules the ES6 Way](http://24ways.org/2014/javascript-modules-the-es6-way/): ES6 模块入门
|
||||
- Axel Rauschmayer, [ECMAScript 6 modules: the final syntax](http://www.2ality.com/2014/09/es6-modules-final.html): ES6 模块的介绍,以及与 CommonJS 规格的详细比较
|
||||
- Dave Herman, [Static module resolution](http://calculist.org/blog/2012/06/29/static-module-resolution/): ES6 模块的静态化设计思想
|
||||
- Jason Orendorff, [ES6 In Depth: Modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/): ES6 模块设计思想的介绍
|
||||
- Ben Newman, [The Importance of import and export](http://benjamn.github.io/empirenode-2015/#/): ES6 模块的设计思想
|
||||
- ESDiscuss, [Why is "export default var a = 1;" invalid syntax?](https://esdiscuss.org/topic/why-is-export-default-var-a-1-invalid-syntax)
|
||||
- Bradley Meck, [ES6 Module Interoperability](https://github.com/nodejs/node-eps/blob/master/002-es6-modules.md): 介绍 Node 如何处理 ES6 语法加载 CommonJS 模块
|
||||
- Axel Rauschmayer, [Making transpiled ES modules more spec-compliant](http://www.2ality.com/2017/01/babel-esm-spec-mode.html): ES6 模块编译成 CommonJS 模块的详细介绍
|
||||
@ -237,16 +237,15 @@
|
||||
|
||||
## 工具
|
||||
|
||||
- Babel, [Babel Handbook](https://github.com/thejameskyle/babel-handbook/tree/master/translations/en): Babel的用法介绍
|
||||
- Google, [traceur-compiler](https://github.com/google/traceur-compiler): Traceur编译器
|
||||
- Babel, [Babel Handbook](https://github.com/thejameskyle/babel-handbook/tree/master/translations/en): Babel 的用法介绍
|
||||
- Google, [traceur-compiler](https://github.com/google/traceur-compiler): Traceur 编译器
|
||||
- Casper Beyer, [ECMAScript 6 Features and Tools](http://caspervonb.github.io/2014/03/05/ecmascript6-features-and-tools.html)
|
||||
- Stoyan Stefanov, [Writing ES6 today with jstransform](http://www.phpied.com/writing-es6-today-with-jstransform/)
|
||||
- ES6 Module Loader, [ES6 Module Loader Polyfill](https://github.com/ModuleLoader/es6-module-loader): 在浏览器和node.js加载ES6模块的一个库,文档里对ES6模块有详细解释
|
||||
- Paul Miller, [es6-shim](https://github.com/paulmillr/es6-shim): 一个针对老式浏览器,模拟ES6部分功能的垫片库(shim)
|
||||
- army8735, [Javascript Downcast](https://github.com/army8735/jsdc): 国产的ES6到ES5的转码器
|
||||
- esnext, [ES6 Module Transpiler](https://github.com/esnext/es6-module-transpiler):基于node.js的将ES6模块转为ES5代码的命令行工具
|
||||
- Sebastian McKenzie, [BabelJS](http://babeljs.io/): ES6转译器
|
||||
- ES6 Module Loader, [ES6 Module Loader Polyfill](https://github.com/ModuleLoader/es6-module-loader): 在浏览器和 node.js 加载 ES6 模块的一个库,文档里对 ES6 模块有详细解释
|
||||
- Paul Miller, [es6-shim](https://github.com/paulmillr/es6-shim): 一个针对老式浏览器,模拟 ES6 部分功能的垫片库(shim)
|
||||
- army8735, [Javascript Downcast](https://github.com/army8735/jsdc): 国产的 ES6 到 ES5 的转码器
|
||||
- esnext, [ES6 Module Transpiler](https://github.com/esnext/es6-module-transpiler):基于 node.js 的将 ES6 模块转为 ES5 代码的命令行工具
|
||||
- Sebastian McKenzie, [BabelJS](http://babeljs.io/): ES6 转译器
|
||||
- SystemJS, [SystemJS](https://github.com/systemjs/systemjs): 在浏览器中加载 AMD、CJS、ES6 模块的一个垫片库
|
||||
- Modernizr, [HTML5 Cross Browser Polyfills](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills#ecmascript-6-harmony): ES6 垫片库清单
|
||||
- Facebook, [regenerator](https://github.com/facebook/regenerator): 将 Generator 函数转为 ES5 的转码器
|
||||
|
||||
|
@ -84,7 +84,7 @@ Reflect.apply(Math.floor, undefined, [1.75]) // 1
|
||||
|
||||
## 静态方法
|
||||
|
||||
`Reflect`对象一共有13个静态方法。
|
||||
`Reflect`对象一共有 13 个静态方法。
|
||||
|
||||
- Reflect.apply(target, thisArg, args)
|
||||
- Reflect.construct(target, args)
|
||||
@ -517,4 +517,3 @@ function set(target, key, value, receiver) {
|
||||
```
|
||||
|
||||
上面代码中,先定义了一个`Set`集合,所有观察者函数都放进这个集合。然后,`observable`函数返回原始对象的代理,拦截赋值操作。拦截函数`set`之中,会自动执行所有观察者。
|
||||
|
||||
|
@ -38,9 +38,9 @@ new RegExp(/abc/ig, 'i').flags
|
||||
|
||||
## 字符串的正则方法
|
||||
|
||||
字符串对象共有4个方法,可以使用正则表达式:`match()`、`replace()`、`search()`和`split()`。
|
||||
字符串对象共有 4 个方法,可以使用正则表达式:`match()`、`replace()`、`search()`和`split()`。
|
||||
|
||||
ES6 将这4个方法,在语言内部全部调用`RegExp`的实例方法,从而做到所有与正则相关的方法,全都定义在`RegExp`对象上。
|
||||
ES6 将这 4 个方法,在语言内部全部调用`RegExp`的实例方法,从而做到所有与正则相关的方法,全都定义在`RegExp`对象上。
|
||||
|
||||
- `String.prototype.match` 调用 `RegExp.prototype[Symbol.match]`
|
||||
- `String.prototype.replace` 调用 `RegExp.prototype[Symbol.replace]`
|
||||
@ -49,7 +49,7 @@ ES6 将这4个方法,在语言内部全部调用`RegExp`的实例方法,从
|
||||
|
||||
## u 修饰符
|
||||
|
||||
ES6 对正则表达式添加了`u`修饰符,含义为“Unicode模式”,用来正确处理大于`\uFFFF`的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。
|
||||
ES6 对正则表达式添加了`u`修饰符,含义为“Unicode 模式”,用来正确处理大于`\uFFFF`的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。
|
||||
|
||||
```javascript
|
||||
/^\uD83D/u.test('\uD83D\uDC2A') // false
|
||||
@ -83,7 +83,7 @@ ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表
|
||||
/\u{20BB7}/u.test('𠮷') // true
|
||||
```
|
||||
|
||||
上面代码表示,如果不加`u`修饰符,正则表达式无法识别`\u{61}`这种表示法,只会认为这匹配61个连续的`u`。
|
||||
上面代码表示,如果不加`u`修饰符,正则表达式无法识别`\u{61}`这种表示法,只会认为这匹配 61 个连续的`u`。
|
||||
|
||||
**(3)量词**
|
||||
|
||||
@ -358,7 +358,7 @@ re.flags // 's'
|
||||
|
||||
## 后行断言
|
||||
|
||||
JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。目前,有一个[提案](https://github.com/goyakin/es-regexp-lookbehind),引入后行断言,V8 引擎4.9版已经支持。
|
||||
JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。目前,有一个[提案](https://github.com/goyakin/es-regexp-lookbehind),引入后行断言,V8 引擎 4.9 版已经支持。
|
||||
|
||||
”先行断言“指的是,`x`只有在`y`前面才匹配,必须写成`/x(?=y)/`。比如,只匹配百分号之前的数字,要写成`/\d+(?=%)/`。”先行否定断言“指的是,`x`只有不在`y`前面才匹配,必须写成`/x(?!y)/`。比如,只匹配不在百分号之前的数字,要写成`/\d+(?!%)/`。
|
||||
|
||||
@ -500,7 +500,7 @@ const month = matchObj.groups.month; // 12
|
||||
const day = matchObj.groups.day; // 31
|
||||
```
|
||||
|
||||
上面代码中,“具名组匹配”在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(`?<year>`),然后就可以在`exec`方法返回结果的`groups`属性上引用该组名。同时,数字序号(` matchObj[1]`)依然有效。
|
||||
上面代码中,“具名组匹配”在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(`?<year>`),然后就可以在`exec`方法返回结果的`groups`属性上引用该组名。同时,数字序号(`matchObj[1]`)依然有效。
|
||||
|
||||
具名组匹配等于为每一组匹配加上了 ID,便于描述匹配的目的。如果组的顺序变了,也不用改变匹配后的处理代码。
|
||||
|
||||
@ -581,4 +581,3 @@ const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/;
|
||||
RE_TWICE.test('abc!abc!abc') // true
|
||||
RE_TWICE.test('abc!abc!ab') // false
|
||||
```
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Set和Map数据结构
|
||||
# Set 和 Map 数据结构
|
||||
|
||||
## Set
|
||||
|
||||
@ -91,7 +91,7 @@ Set 结构的实例有以下属性。
|
||||
|
||||
Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
|
||||
|
||||
- `add(value)`:添加某个值,返回Set结构本身。
|
||||
- `add(value)`:添加某个值,返回 Set 结构本身。
|
||||
- `delete(value)`:删除某个值,返回一个布尔值,表示删除是否成功。
|
||||
- `has(value)`:返回一个布尔值,表示该值是否为`Set`的成员。
|
||||
- `clear()`:清除所有成员,没有返回值。
|
||||
@ -162,7 +162,7 @@ Set 结构的实例有四个遍历方法,可以用于遍历成员。
|
||||
- `entries()`:返回键值对的遍历器
|
||||
- `forEach()`:使用回调函数遍历每个成员
|
||||
|
||||
需要特别指出的是,`Set`的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用Set保存一个回调函数列表,调用时就能保证按照添加顺序调用。
|
||||
需要特别指出的是,`Set`的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
|
||||
|
||||
**(1)`keys()`,`values()`,`entries()`**
|
||||
|
||||
@ -373,7 +373,7 @@ ws.delete(window);
|
||||
ws.has(window); // false
|
||||
```
|
||||
|
||||
WeakSet没有`size`属性,没有办法遍历它的成员。
|
||||
WeakSet 没有`size`属性,没有办法遍历它的成员。
|
||||
|
||||
```javascript
|
||||
ws.size // undefined
|
||||
@ -403,7 +403,7 @@ class Foo {
|
||||
}
|
||||
```
|
||||
|
||||
上面代码保证了`Foo`的实例方法,只能在`Foo`的实例上调用。这里使用WeakSet的好处是,`foos`对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑`foos`,也不会出现内存泄漏。
|
||||
上面代码保证了`Foo`的实例方法,只能在`Foo`的实例上调用。这里使用 WeakSet 的好处是,`foos`对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑`foos`,也不会出现内存泄漏。
|
||||
|
||||
## Map
|
||||
|
||||
@ -421,7 +421,7 @@ data['[object HTMLDivElement]'] // "metadata"
|
||||
|
||||
上面代码原意是将一个 DOM 节点作为对象`data`的键,但是由于对象只接受字符串作为键名,所以`element`被自动转为字符串`[object HTMLDivElement]`。
|
||||
|
||||
为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
|
||||
为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
|
||||
|
||||
```javascript
|
||||
const m = new Map();
|
||||
@ -562,7 +562,7 @@ map.get(NaN) // 123
|
||||
|
||||
Map 结构的实例有以下属性和操作方法。
|
||||
|
||||
**(1)size属性**
|
||||
**(1)size 属性**
|
||||
|
||||
`size`属性返回 Map 结构的成员总数。
|
||||
|
||||
@ -877,7 +877,7 @@ jsonToStrMap('{"yes": true, "no": false}')
|
||||
// Map {'yes' => true, 'no' => false}
|
||||
```
|
||||
|
||||
但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为Map。这往往是数组转为 JSON 的逆操作。
|
||||
但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是数组转为 JSON 的逆操作。
|
||||
|
||||
```javascript
|
||||
function jsonToMap(jsonStr) {
|
||||
|
135
docs/simd.md
135
docs/simd.md
@ -4,9 +4,9 @@
|
||||
|
||||
SIMD(发音`/sim-dee/`)是“Single Instruction/Multiple Data”的缩写,意为“单指令,多数据”。它是 JavaScript 操作 CPU 对应指令的接口,你可以看做这是一种不同的运算执行模式。与它相对的是 SISD(“Single Instruction/Single Data”),即“单指令,单数据”。
|
||||
|
||||
SIMD 的含义是使用一个指令,完成多个数据的运算;SISD 的含义是使用一个指令,完成单个数据的运算,这是 JavaScript 的默认运算模式。显而易见,SIMD 的执行效率要高于 SISD,所以被广泛用于3D图形运算、物理模拟等运算量超大的项目之中。
|
||||
SIMD 的含义是使用一个指令,完成多个数据的运算;SISD 的含义是使用一个指令,完成单个数据的运算,这是 JavaScript 的默认运算模式。显而易见,SIMD 的执行效率要高于 SISD,所以被广泛用于 3D 图形运算、物理模拟等运算量超大的项目之中。
|
||||
|
||||
为了理解SIMD,请看下面的例子。
|
||||
为了理解 SIMD,请看下面的例子。
|
||||
|
||||
```javascript
|
||||
var a = [1, 2, 3, 4];
|
||||
@ -20,7 +20,7 @@ c[3] = a[3] + b[3];
|
||||
c // Array[6, 8, 10, 12]
|
||||
```
|
||||
|
||||
上面代码中,数组`a`和`b`的对应成员相加,结果放入数组`c`。它的运算模式是依次处理每个数组成员,一共有四个数组成员,所以需要运算4次。
|
||||
上面代码中,数组`a`和`b`的对应成员相加,结果放入数组`c`。它的运算模式是依次处理每个数组成员,一共有四个数组成员,所以需要运算 4 次。
|
||||
|
||||
如果采用 SIMD 模式,只要运算一次就够了。
|
||||
|
||||
@ -30,7 +30,7 @@ var b = SIMD.Float32x4(5, 6, 7, 8);
|
||||
var c = SIMD.Float32x4.add(a, b); // Float32x4[6, 8, 10, 12]
|
||||
```
|
||||
|
||||
上面代码之中,数组`a`和`b`的四个成员的各自相加,只用一条指令就完成了。因此,速度比上一种写法提高了4倍。
|
||||
上面代码之中,数组`a`和`b`的四个成员的各自相加,只用一条指令就完成了。因此,速度比上一种写法提高了 4 倍。
|
||||
|
||||
一次 SIMD 运算,可以处理多个数据,这些数据被称为“通道”(lane)。上面代码中,一次运算了四个数据,因此就是四个通道。
|
||||
|
||||
@ -41,34 +41,34 @@ v + w = 〈v1, …, vn〉+ 〈w1, …, wn〉
|
||||
= 〈v1+w1, …, vn+wn〉
|
||||
```
|
||||
|
||||
上面代码中,`v`和`w`是两个多元矢量。它们的加运算,在 SIMD 下是一个指令、而不是 n 个指令完成的,这就大大提高了效率。这对于3D动画、图像处理、信号处理、数值处理、加密等运算是非常重要的。比如,Canvas的`getImageData()`会将图像文件读成一个二进制数组,SIMD 就很适合对于这种数组的处理。
|
||||
上面代码中,`v`和`w`是两个多元矢量。它们的加运算,在 SIMD 下是一个指令、而不是 n 个指令完成的,这就大大提高了效率。这对于 3D 动画、图像处理、信号处理、数值处理、加密等运算是非常重要的。比如,Canvas 的`getImageData()`会将图像文件读成一个二进制数组,SIMD 就很适合对于这种数组的处理。
|
||||
|
||||
总的来说,SIMD 是数据并行处理(parallelism)的一种手段,可以加速一些运算密集型操作的速度。将来与 WebAssembly 结合以后,可以让 JavaScript 达到二进制代码的运行速度。
|
||||
|
||||
## 数据类型
|
||||
|
||||
SIMD 提供12种数据类型,总长度都是128个二进制位。
|
||||
SIMD 提供 12 种数据类型,总长度都是 128 个二进制位。
|
||||
|
||||
- Float32x4:四个32位浮点数
|
||||
- Float64x2:两个64位浮点数
|
||||
- Int32x4:四个32位整数
|
||||
- Int16x8:八个16位整数
|
||||
- Int8x16:十六个8位整数
|
||||
- Uint32x4:四个无符号的32位整数
|
||||
- Uint16x8:八个无符号的16位整数
|
||||
- Uint8x16:十六个无符号的8位整数
|
||||
- Bool32x4:四个32位布尔值
|
||||
- Bool16x8:八个16位布尔值
|
||||
- Bool8x16:十六个8位布尔值
|
||||
- Bool64x2:两个64位布尔值
|
||||
- Float32x4:四个 32 位浮点数
|
||||
- Float64x2:两个 64 位浮点数
|
||||
- Int32x4:四个 32 位整数
|
||||
- Int16x8:八个 16 位整数
|
||||
- Int8x16:十六个 8 位整数
|
||||
- Uint32x4:四个无符号的 32 位整数
|
||||
- Uint16x8:八个无符号的 16 位整数
|
||||
- Uint8x16:十六个无符号的 8 位整数
|
||||
- Bool32x4:四个 32 位布尔值
|
||||
- Bool16x8:八个 16 位布尔值
|
||||
- Bool8x16:十六个 8 位布尔值
|
||||
- Bool64x2:两个 64 位布尔值
|
||||
|
||||
每种数据类型被`x`符号分隔成两部分,后面的部分表示通道数,前面的部分表示每个通道的宽度和类型。比如,`Float32x4`就表示这个值有4个通道,每个通道是一个32位浮点数。
|
||||
每种数据类型被`x`符号分隔成两部分,后面的部分表示通道数,前面的部分表示每个通道的宽度和类型。比如,`Float32x4`就表示这个值有 4 个通道,每个通道是一个 32 位浮点数。
|
||||
|
||||
每个通道之中,可以放置四种数据。
|
||||
|
||||
- 浮点数(float,比如1.0)
|
||||
- 浮点数(float,比如 1.0)
|
||||
- 带符号的整数(Int,比如-1)
|
||||
- 无符号的整数(Uint,比如1)
|
||||
- 无符号的整数(Uint,比如 1)
|
||||
- 布尔值(Bool,包含`true`和`false`两种值)
|
||||
|
||||
每种 SIMD 的数据类型都是一个函数方法,可以传入参数,生成对应的值。
|
||||
@ -77,7 +77,7 @@ SIMD 提供12种数据类型,总长度都是128个二进制位。
|
||||
var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
|
||||
```
|
||||
|
||||
上面代码中,变量`a`就是一个128位、包含四个32位浮点数(即四个通道)的值。
|
||||
上面代码中,变量`a`就是一个 128 位、包含四个 32 位浮点数(即四个通道)的值。
|
||||
|
||||
注意,这些数据类型方法都不是构造函数,前面不能加`new`,否则会报错。
|
||||
|
||||
@ -92,7 +92,7 @@ var v = new SIMD.Float32x4(0, 1, 2, 3);
|
||||
|
||||
### SIMD.%type%.abs(),SIMD.%type%.neg()
|
||||
|
||||
`abs`方法接受一个SIMD值作为参数,将它的每个通道都转成绝对值,作为一个新的SIMD值返回。
|
||||
`abs`方法接受一个 SIMD 值作为参数,将它的每个通道都转成绝对值,作为一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(-1, -2, 0, NaN);
|
||||
@ -100,7 +100,7 @@ SIMD.Float32x4.abs(a)
|
||||
// Float32x4[1, 2, 0, NaN]
|
||||
```
|
||||
|
||||
`neg`方法接受一个SIMD值作为参数,将它的每个通道都转成负值,作为一个新的SIMD值返回。
|
||||
`neg`方法接受一个 SIMD 值作为参数,将它的每个通道都转成负值,作为一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(-1, -2, 3, 0);
|
||||
@ -114,7 +114,7 @@ SIMD.Float64x2.neg(b)
|
||||
|
||||
### SIMD.%type%.add(),SIMD.%type%.addSaturate()
|
||||
|
||||
`add`方法接受两个SIMD值作为参数,将它们的每个通道相加,作为一个新的SIMD值返回。
|
||||
`add`方法接受两个 SIMD 值作为参数,将它们的每个通道相加,作为一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
|
||||
@ -122,7 +122,7 @@ var b = SIMD.Float32x4(5.0, 10.0, 15.0, 20.0);
|
||||
var c = SIMD.Float32x4.add(a, b);
|
||||
```
|
||||
|
||||
上面代码中,经过加法运算,新的SIMD值为`(6.0, 12.0, 18.0. 24.0)`。
|
||||
上面代码中,经过加法运算,新的 SIMD 值为`(6.0, 12.0, 18.0. 24.0)`。
|
||||
|
||||
`addSaturate`方法跟`add`方法的作用相同,都是两个通道相加,但是溢出的处理不一致。对于`add`方法,如果两个值相加发生溢出,溢出的二进制位会被丢弃; `addSaturate`方法则是返回该数据类型的最大值。
|
||||
|
||||
@ -138,13 +138,13 @@ SIMD.Int16x8.addSaturate(c, d);
|
||||
// Int16x8[32766, 32767, 32767, 32767, 2, 2, 2, 2]
|
||||
```
|
||||
|
||||
上面代码中,`Uint16`的最大值是65535,`Int16`的最大值是32767。一旦发生溢出,就返回这两个值。
|
||||
上面代码中,`Uint16`的最大值是 65535,`Int16`的最大值是 32767。一旦发生溢出,就返回这两个值。
|
||||
|
||||
注意,`Uint32x4`和`Int32x4`这两种数据类型没有`addSaturate`方法。
|
||||
|
||||
### SIMD.%type%.sub(),SIMD.%type%.subSaturate()
|
||||
|
||||
`sub`方法接受两个SIMD值作为参数,将它们的每个通道相减,作为一个新的SIMD值返回。
|
||||
`sub`方法接受两个 SIMD 值作为参数,将它们的每个通道相减,作为一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(-1, -2, 3, 4);
|
||||
@ -171,7 +171,7 @@ SIMD.Int16x8.subSaturate(c, d)
|
||||
|
||||
### SIMD.%type%.mul(),SIMD.%type%.div(),SIMD.%type%.sqrt()
|
||||
|
||||
`mul`方法接受两个SIMD值作为参数,将它们的每个通道相乘,作为一个新的SIMD值返回。
|
||||
`mul`方法接受两个 SIMD 值作为参数,将它们的每个通道相乘,作为一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(-1, -2, 3, 4);
|
||||
@ -180,7 +180,7 @@ SIMD.Float32x4.mul(a, b)
|
||||
// Float32x4[-3, -6, 9, 12]
|
||||
```
|
||||
|
||||
`div`方法接受两个SIMD值作为参数,将它们的每个通道相除,作为一个新的SIMD值返回。
|
||||
`div`方法接受两个 SIMD 值作为参数,将它们的每个通道相除,作为一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(2, 2, 2, 2);
|
||||
@ -189,7 +189,7 @@ SIMD.Float32x4.div(a, b)
|
||||
// Float32x4[0.5, 0.5, 0.5, 0.5]
|
||||
```
|
||||
|
||||
`sqrt`方法接受一个SIMD值作为参数,求出每个通道的平方根,作为一个新的SIMD值返回。
|
||||
`sqrt`方法接受一个 SIMD 值作为参数,求出每个通道的平方根,作为一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var b = SIMD.Float64x2(4, 8);
|
||||
@ -199,7 +199,7 @@ SIMD.Float64x2.sqrt(b)
|
||||
|
||||
### SIMD.%FloatType%.reciprocalApproximation(),SIMD.%type%.reciprocalSqrtApproximation()
|
||||
|
||||
`reciprocalApproximation`方法接受一个SIMD值作为参数,求出每个通道的倒数(`1 / x`),作为一个新的SIMD值返回。
|
||||
`reciprocalApproximation`方法接受一个 SIMD 值作为参数,求出每个通道的倒数(`1 / x`),作为一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(1, 2, 3, 4);
|
||||
@ -207,7 +207,7 @@ SIMD.Float32x4.reciprocalApproximation(a);
|
||||
// Float32x4[1, 0.5, 0.3333333432674408, 0.25]
|
||||
```
|
||||
|
||||
`reciprocalSqrtApproximation`方法接受一个SIMD值作为参数,求出每个通道的平方根的倒数(`1 / (x^0.5)`),作为一个新的SIMD值返回。
|
||||
`reciprocalSqrtApproximation`方法接受一个 SIMD 值作为参数,求出每个通道的平方根的倒数(`1 / (x^0.5)`),作为一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(1, 2, 3, 4);
|
||||
@ -219,7 +219,7 @@ SIMD.Float32x4.reciprocalSqrtApproximation(a)
|
||||
|
||||
### SIMD.%IntegerType%.shiftLeftByScalar()
|
||||
|
||||
`shiftLeftByScalar`方法接受一个SIMD值作为参数,然后将每个通道的值左移指定的位数,作为一个新的SIMD值返回。
|
||||
`shiftLeftByScalar`方法接受一个 SIMD 值作为参数,然后将每个通道的值左移指定的位数,作为一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Int32x4(1, 2, 4, 8);
|
||||
@ -239,7 +239,7 @@ var jx4 = SIMD.Int32x4.shiftLeftByScalar(ix4, 32);
|
||||
|
||||
### SIMD.%IntegerType%.shiftRightByScalar()
|
||||
|
||||
`shiftRightByScalar`方法接受一个SIMD值作为参数,然后将每个通道的值右移指定的位数,返回一个新的SIMD值。
|
||||
`shiftRightByScalar`方法接受一个 SIMD 值作为参数,然后将每个通道的值右移指定的位数,返回一个新的 SIMD 值。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Int32x4(1, 2, 4, -8);
|
||||
@ -255,7 +255,7 @@ SIMD.Uint32x4.shiftRightByScalar(a, 1);
|
||||
// Uint32x4[0, 1, 2, 2147483644]
|
||||
```
|
||||
|
||||
上面代码中,`-8`右移一位变成了`2147483644`,是因为对于32位无符号整数来说,`-8`的二进制形式是`11111111111111111111111111111000`,右移一位就变成了`01111111111111111111111111111100`,相当于`2147483644`。
|
||||
上面代码中,`-8`右移一位变成了`2147483644`,是因为对于 32 位无符号整数来说,`-8`的二进制形式是`11111111111111111111111111111000`,右移一位就变成了`01111111111111111111111111111100`,相当于`2147483644`。
|
||||
|
||||
注意,只有整数的数据类型才有这个方法。
|
||||
|
||||
@ -263,7 +263,7 @@ SIMD.Uint32x4.shiftRightByScalar(a, 1);
|
||||
|
||||
### SIMD.%type%.check()
|
||||
|
||||
`check`方法用于检查一个值是否为当前类型的SIMD值。如果是的,就返回这个值,否则就报错。
|
||||
`check`方法用于检查一个值是否为当前类型的 SIMD 值。如果是的,就返回这个值,否则就报错。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(1, 2, 3, 9);
|
||||
@ -278,14 +278,14 @@ SIMD.Int32x4.check('hello world') // 报错
|
||||
|
||||
### SIMD.%type%.extractLane(),SIMD.%type%.replaceLane()
|
||||
|
||||
`extractLane`方法用于返回给定通道的值。它接受两个参数,分别是SIMD值和通道编号。
|
||||
`extractLane`方法用于返回给定通道的值。它接受两个参数,分别是 SIMD 值和通道编号。
|
||||
|
||||
```javascript
|
||||
var t = SIMD.Float32x4(1, 2, 3, 4);
|
||||
SIMD.Float32x4.extractLane(t, 2) // 3
|
||||
```
|
||||
|
||||
`replaceLane`方法用于替换指定通道的值,并返回一个新的SIMD值。它接受三个参数,分别是原来的SIMD值、通道编号和新的通道值。
|
||||
`replaceLane`方法用于替换指定通道的值,并返回一个新的 SIMD 值。它接受三个参数,分别是原来的 SIMD 值、通道编号和新的通道值。
|
||||
|
||||
```javascript
|
||||
var t = SIMD.Float32x4(1, 2, 3, 4);
|
||||
@ -295,7 +295,7 @@ SIMD.Float32x4.replaceLane(t, 2, 42)
|
||||
|
||||
### SIMD.%type%.load()
|
||||
|
||||
`load`方法用于从二进制数组读入数据,生成一个新的SIMD值。
|
||||
`load`方法用于从二进制数组读入数据,生成一个新的 SIMD 值。
|
||||
|
||||
```javascript
|
||||
var a = new Int32Array([1,2,3,4,5,6,7,8]);
|
||||
@ -307,7 +307,7 @@ SIMD.Int32x4.load(a, 2);
|
||||
// Int32x4[3, 4, 5, 6]
|
||||
```
|
||||
|
||||
`load`方法接受两个参数:一个二进制数组和开始读取的位置(从0开始)。如果位置不合法(比如`-1`或者超出二进制数组的大小),就会抛出一个错误。
|
||||
`load`方法接受两个参数:一个二进制数组和开始读取的位置(从 0 开始)。如果位置不合法(比如`-1`或者超出二进制数组的大小),就会抛出一个错误。
|
||||
|
||||
这个方法还有三个变种`load1()`、`load2()`、`load3()`,表示从指定位置开始,只加载一个通道、二个通道、三个通道的值。
|
||||
|
||||
@ -330,7 +330,7 @@ SIMD.Int32x4.load3(a, 0);
|
||||
|
||||
### SIMD.%type%.store()
|
||||
|
||||
`store`方法用于将一个SIMD值,写入一个二进制数组。它接受三个参数,分别是二进制数组、开始写入的数组位置、SIMD值。它返回写入值以后的二进制数组。
|
||||
`store`方法用于将一个 SIMD 值,写入一个二进制数组。它接受三个参数,分别是二进制数组、开始写入的数组位置、SIMD 值。它返回写入值以后的二进制数组。
|
||||
|
||||
```javascript
|
||||
var t1 = new Int32Array(8);
|
||||
@ -344,7 +344,7 @@ SIMD.Int32x4.store(t2, 2, v2)
|
||||
// Int32Array[0, 0, 1, 2, 3, 4, 0, 0]
|
||||
```
|
||||
|
||||
上面代码中,`t1`是一个二进制数组,`v1`是一个SIMD值,只有四个通道。所以写入`t1`以后,只有前四个位置有值,后四个位置都是0。而`t2`是从2号位置开始写入,所以前两个位置和后两个位置都是0。
|
||||
上面代码中,`t1`是一个二进制数组,`v1`是一个 SIMD 值,只有四个通道。所以写入`t1`以后,只有前四个位置有值,后四个位置都是 0。而`t2`是从 2 号位置开始写入,所以前两个位置和后两个位置都是 0。
|
||||
|
||||
这个方法还有三个变种`store1()`、`store2()`和`store3()`,表示只写入一个通道、二个通道和三个通道的值。
|
||||
|
||||
@ -357,7 +357,7 @@ SIMD.Int32x4.store1(tarray, 0, value);
|
||||
|
||||
### SIMD.%type%.splat()
|
||||
|
||||
`splat`方法返回一个新的SIMD值,该值的所有通道都会设成同一个预先给定的值。
|
||||
`splat`方法返回一个新的 SIMD 值,该值的所有通道都会设成同一个预先给定的值。
|
||||
|
||||
```javascript
|
||||
SIMD.Float32x4.splat(3);
|
||||
@ -366,11 +366,11 @@ SIMD.Float64x2.splat(3);
|
||||
// Float64x2[3, 3]
|
||||
```
|
||||
|
||||
如果省略参数,所有整数型的SIMD值都会设定`0`,浮点型的SIMD值都会设成`NaN`。
|
||||
如果省略参数,所有整数型的 SIMD 值都会设定`0`,浮点型的 SIMD 值都会设成`NaN`。
|
||||
|
||||
### SIMD.%type%.swizzle()
|
||||
|
||||
`swizzle`方法返回一个新的SIMD值,重新排列原有的SIMD值的通道顺序。
|
||||
`swizzle`方法返回一个新的 SIMD 值,重新排列原有的 SIMD 值的通道顺序。
|
||||
|
||||
```javascript
|
||||
var t = SIMD.Float32x4(1, 2, 3, 4);
|
||||
@ -378,7 +378,7 @@ SIMD.Float32x4.swizzle(t, 1, 2, 0, 3);
|
||||
// Float32x4[2,3,1,4]
|
||||
```
|
||||
|
||||
上面代码中,`swizzle`方法的第一个参数是原有的SIMD值,后面的参数对应将要返回的SIMD值的四个通道。它的意思是新的SIMD的四个通道,依次是原来SIMD值的1号通道、2号通道、0号通道、3号通道。由于SIMD值最多可以有16个通道,所以`swizzle`方法除了第一个参数以外,最多还可以接受16个参数。
|
||||
上面代码中,`swizzle`方法的第一个参数是原有的 SIMD 值,后面的参数对应将要返回的 SIMD 值的四个通道。它的意思是新的 SIMD 的四个通道,依次是原来 SIMD 值的 1 号通道、2 号通道、0 号通道、3 号通道。由于 SIMD 值最多可以有 16 个通道,所以`swizzle`方法除了第一个参数以外,最多还可以接受 16 个参数。
|
||||
|
||||
下面是另一个例子。
|
||||
|
||||
@ -398,7 +398,7 @@ var d = SIMD.Float32x4.swizzle(a, 3, 2, 1, 0);
|
||||
|
||||
### SIMD.%type%.shuffle()
|
||||
|
||||
`shuffle`方法从两个SIMD值之中取出指定通道,返回一个新的SIMD值。
|
||||
`shuffle`方法从两个 SIMD 值之中取出指定通道,返回一个新的 SIMD 值。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(1, 2, 3, 4);
|
||||
@ -408,13 +408,13 @@ SIMD.Float32x4.shuffle(a, b, 1, 5, 7, 2);
|
||||
// Float32x4[2, 6, 8, 3]
|
||||
```
|
||||
|
||||
上面代码中,`a`和`b`一共有8个通道,依次编号为0到7。`shuffle`根据编号,取出相应的通道,返回一个新的SIMD值。
|
||||
上面代码中,`a`和`b`一共有 8 个通道,依次编号为 0 到 7。`shuffle`根据编号,取出相应的通道,返回一个新的 SIMD 值。
|
||||
|
||||
## 静态方法:比较运算
|
||||
|
||||
### SIMD.%type%.equal(),SIMD.%type%.notEqual()
|
||||
|
||||
`equal`方法用来比较两个SIMD值`a`和`b`的每一个通道,根据两者是否精确相等(`a === b`),得到一个布尔值。最后,所有通道的比较结果,组成一个新的SIMD值,作为掩码返回。`notEqual`方法则是比较两个通道是否不相等(`a !== b`)。
|
||||
`equal`方法用来比较两个 SIMD 值`a`和`b`的每一个通道,根据两者是否精确相等(`a === b`),得到一个布尔值。最后,所有通道的比较结果,组成一个新的 SIMD 值,作为掩码返回。`notEqual`方法则是比较两个通道是否不相等(`a !== b`)。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(1, 2, 3, 9);
|
||||
@ -429,7 +429,7 @@ SIMD.Float32x4.notEqual(a,b);
|
||||
|
||||
### SIMD.%type%.greaterThan(),SIMD.%type%.greaterThanOrEqual()
|
||||
|
||||
`greatThan`方法用来比较两个SIMD值`a`和`b`的每一个通道,如果在该通道中,`a`较大就得到`true`,否则得到`false`。最后,所有通道的比较结果,组成一个新的SIMD值,作为掩码返回。`greaterThanOrEqual`则是比较`a`是否大于等于`b`。
|
||||
`greatThan`方法用来比较两个 SIMD 值`a`和`b`的每一个通道,如果在该通道中,`a`较大就得到`true`,否则得到`false`。最后,所有通道的比较结果,组成一个新的 SIMD 值,作为掩码返回。`greaterThanOrEqual`则是比较`a`是否大于等于`b`。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(1, 6, 3, 11);
|
||||
@ -444,7 +444,7 @@ SIMD.Float32x4.greaterThanOrEqual(a, b)
|
||||
|
||||
### SIMD.%type%.lessThan(),SIMD.%type%.lessThanOrEqual()
|
||||
|
||||
`lessThan`方法用来比较两个SIMD值`a`和`b`的每一个通道,如果在该通道中,`a`较小就得到`true`,否则得到`false`。最后,所有通道的比较结果,会组成一个新的SIMD值,作为掩码返回。`lessThanOrEqual`方法则是比较`a`是否等于`b`。
|
||||
`lessThan`方法用来比较两个 SIMD 值`a`和`b`的每一个通道,如果在该通道中,`a`较小就得到`true`,否则得到`false`。最后,所有通道的比较结果,会组成一个新的 SIMD 值,作为掩码返回。`lessThanOrEqual`方法则是比较`a`是否等于`b`。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(1, 2, 3, 11);
|
||||
@ -459,7 +459,7 @@ SIMD.Float32x4.lessThanOrEqual(a, b)
|
||||
|
||||
### SIMD.%type%.select()
|
||||
|
||||
`select`方法通过掩码生成一个新的SIMD值。它接受三个参数,分别是掩码和两个SIMD值。
|
||||
`select`方法通过掩码生成一个新的 SIMD 值。它接受三个参数,分别是掩码和两个 SIMD 值。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(1, 2, 3, 4);
|
||||
@ -471,7 +471,7 @@ SIMD.Float32x4.select(mask, a, b);
|
||||
// Float32x4[1, 6, 7, 4]
|
||||
```
|
||||
|
||||
上面代码中,`select`方法接受掩码和两个SIMD值作为参数。当某个通道对应的掩码为`true`时,会选择第一个SIMD值的对应通道,否则选择第二个SIMD值的对应通道。
|
||||
上面代码中,`select`方法接受掩码和两个 SIMD 值作为参数。当某个通道对应的掩码为`true`时,会选择第一个 SIMD 值的对应通道,否则选择第二个 SIMD 值的对应通道。
|
||||
|
||||
这个方法通常与比较运算符结合使用。
|
||||
|
||||
@ -486,11 +486,11 @@ var result = SIMD.Float32x4.select(mask, a, b);
|
||||
// Float32x4[0, 6, 3, 4]
|
||||
```
|
||||
|
||||
上面代码中,先通过`lessThan`方法生成一个掩码,然后通过`select`方法生成一个由每个通道的较小值组成的新的SIMD值。
|
||||
上面代码中,先通过`lessThan`方法生成一个掩码,然后通过`select`方法生成一个由每个通道的较小值组成的新的 SIMD 值。
|
||||
|
||||
### SIMD.%BooleanType%.allTrue(),SIMD.%BooleanType%.anyTrue()
|
||||
|
||||
`allTrue`方法接受一个SIMD值作为参数,然后返回一个布尔值,表示该SIMD值的所有通道是否都为`true`。
|
||||
`allTrue`方法接受一个 SIMD 值作为参数,然后返回一个布尔值,表示该 SIMD 值的所有通道是否都为`true`。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Bool32x4(true, true, true, true);
|
||||
@ -524,7 +524,7 @@ var b2 = SIMD.Int32x4.anyTrue(ix4); // true
|
||||
|
||||
### SIMD.%type%.min(),SIMD.%type%.minNum()
|
||||
|
||||
`min`方法接受两个SIMD值作为参数,将两者的对应通道的较小值,组成一个新的SIMD值返回。
|
||||
`min`方法接受两个 SIMD 值作为参数,将两者的对应通道的较小值,组成一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(-1, -2, 3, 5.2);
|
||||
@ -555,7 +555,7 @@ var dx4 = SIMD.Float32x4.minNum(ax4, bx4);
|
||||
|
||||
### SIMD.%type%.max(),SIMD.%type%.maxNum()
|
||||
|
||||
`max`方法接受两个SIMD值作为参数,将两者的对应通道的较大值,组成一个新的SIMD值返回。
|
||||
`max`方法接受两个 SIMD 值作为参数,将两者的对应通道的较大值,组成一个新的 SIMD 值返回。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(-1, -2, 3, 5.2);
|
||||
@ -586,7 +586,7 @@ SIMD.Float64x2.maxNum(c, d)
|
||||
|
||||
### SIMD.%type%.and(),SIMD.%type%.or(),SIMD.%type%.xor(),SIMD.%type%.not()
|
||||
|
||||
`and`方法接受两个SIMD值作为参数,返回两者对应的通道进行二进制`AND`运算(`&`)后得到的新的SIMD值。
|
||||
`and`方法接受两个 SIMD 值作为参数,返回两者对应的通道进行二进制`AND`运算(`&`)后得到的新的 SIMD 值。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Int32x4(1, 2, 4, 8);
|
||||
@ -597,7 +597,7 @@ SIMD.Int32x4.and(a, b)
|
||||
|
||||
上面代码中,以通道`0`为例,`1`的二进制形式是`0001`,`5`的二进制形式是`01001`,所以进行`AND`运算以后,得到`0001`。
|
||||
|
||||
`or`方法接受两个SIMD值作为参数,返回两者对应的通道进行二进制`OR`运算(`|`)后得到的新的SIMD值。
|
||||
`or`方法接受两个 SIMD 值作为参数,返回两者对应的通道进行二进制`OR`运算(`|`)后得到的新的 SIMD 值。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Int32x4(1, 2, 4, 8);
|
||||
@ -606,7 +606,7 @@ SIMD.Int32x4.or(a, b)
|
||||
// Int32x4[5, 7, 5, 13]
|
||||
```
|
||||
|
||||
`xor`方法接受两个SIMD值作为参数,返回两者对应的通道进行二进制”异或“运算(`^`)后得到的新的SIMD值。
|
||||
`xor`方法接受两个 SIMD 值作为参数,返回两者对应的通道进行二进制”异或“运算(`^`)后得到的新的 SIMD 值。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Int32x4(1, 2, 4, 8);
|
||||
@ -615,7 +615,7 @@ SIMD.Int32x4.xor(a, b)
|
||||
// Int32x4[4, 7, 1, 13]
|
||||
```
|
||||
|
||||
`not`方法接受一个SIMD值作为参数,返回每个通道进行二进制”否“运算(`~`)后得到的新的SIMD值。
|
||||
`not`方法接受一个 SIMD 值作为参数,返回每个通道进行二进制”否“运算(`~`)后得到的新的 SIMD 值。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Int32x4(1, 2, 4, 8);
|
||||
@ -623,11 +623,11 @@ SIMD.Int32x4.not(a)
|
||||
// Int32x4[-2, -3, -5, -9]
|
||||
```
|
||||
|
||||
上面代码中,`1`的否运算之所以得到`-2`,是因为在计算机内部,负数采用”2的补码“这种形式进行表示。也就是说,整数`n`的负数形式`-n`,是对每一个二进制位取反以后,再加上1。因此,直接取反就相当于负数形式再减去1,比如`1`的负数形式是`-1`,再减去1,就得到了`-2`。
|
||||
上面代码中,`1`的否运算之所以得到`-2`,是因为在计算机内部,负数采用”2 的补码“这种形式进行表示。也就是说,整数`n`的负数形式`-n`,是对每一个二进制位取反以后,再加上 1。因此,直接取反就相当于负数形式再减去 1,比如`1`的负数形式是`-1`,再减去 1,就得到了`-2`。
|
||||
|
||||
## 静态方法:数据类型转换
|
||||
|
||||
SIMD提供以下方法,用来将一种数据类型转为另一种数据类型。
|
||||
SIMD 提供以下方法,用来将一种数据类型转为另一种数据类型。
|
||||
|
||||
- `SIMD.%type%.fromFloat32x4()`
|
||||
- `SIMD.%type%.fromFloat32x4Bits()`
|
||||
@ -662,7 +662,7 @@ SIMD.Int16x8.fromFloat32x4Bits(t);
|
||||
// Int16x8[0, 16256, 0, 16384, 0, 16448, 0, 16512]
|
||||
```
|
||||
|
||||
上面代码中,原始SIMD值`t`是4通道的,而目标值是8通道的。
|
||||
上面代码中,原始 SIMD 值`t`是 4 通道的,而目标值是 8 通道的。
|
||||
|
||||
如果数据转换时,原通道的数据大小,超过了目标通道的最大宽度,就会报错。
|
||||
|
||||
@ -670,7 +670,7 @@ SIMD.Int16x8.fromFloat32x4Bits(t);
|
||||
|
||||
### SIMD.%type%.prototype.toString()
|
||||
|
||||
`toString`方法返回一个SIMD值的字符串形式。
|
||||
`toString`方法返回一个 SIMD 值的字符串形式。
|
||||
|
||||
```javascript
|
||||
var a = SIMD.Float32x4(11, 22, 33, 44);
|
||||
@ -692,7 +692,7 @@ function average(list) {
|
||||
}
|
||||
```
|
||||
|
||||
使用SIMD,可以将计算次数减少到`n`次的四分之一。
|
||||
使用 SIMD,可以将计算次数减少到`n`次的四分之一。
|
||||
|
||||
```javascript
|
||||
function average(list) {
|
||||
@ -713,4 +713,3 @@ function average(list) {
|
||||
```
|
||||
|
||||
上面代码先是每隔四位,将所有的值读入一个 SIMD,然后立刻累加。然后,得到累加值四个通道的总和,再除以`n`就可以了。
|
||||
|
||||
|
110
docs/spec.md
110
docs/spec.md
@ -6,17 +6,17 @@
|
||||
|
||||
一般来说,没有必要阅读规格,除非你要写编译器。因为规格写得非常抽象和精炼,又缺乏实例,不容易理解,而且对于解决实际的应用问题,帮助不大。但是,如果你遇到疑难的语法问题,实在找不到答案,这时可以去查看规格文件,了解语言标准是怎么说的。规格是解决问题的“最后一招”。
|
||||
|
||||
这对JavaScript语言很有必要。因为它的使用场景复杂,语法规则不统一,例外很多,各种运行环境的行为不一致,导致奇怪的语法问题层出不穷,任何语法书都不可能囊括所有情况。查看规格,不失为一种解决语法问题的最可靠、最权威的终极方法。
|
||||
这对 JavaScript 语言很有必要。因为它的使用场景复杂,语法规则不统一,例外很多,各种运行环境的行为不一致,导致奇怪的语法问题层出不穷,任何语法书都不可能囊括所有情况。查看规格,不失为一种解决语法问题的最可靠、最权威的终极方法。
|
||||
|
||||
本章介绍如何读懂ECMAScript 6的规格文件。
|
||||
本章介绍如何读懂 ECMAScript 6 的规格文件。
|
||||
|
||||
ECMAScript 6的规格,可以在ECMA国际标准组织的官方网站([www.ecma-international.org/ecma-262/6.0/](http://www.ecma-international.org/ecma-262/6.0/))免费下载和在线阅读。
|
||||
ECMAScript 6 的规格,可以在 ECMA 国际标准组织的官方网站([www.ecma-international.org/ecma-262/6.0/](http://www.ecma-international.org/ecma-262/6.0/))免费下载和在线阅读。
|
||||
|
||||
这个规格文件相当庞大,一共有26章,A4打印的话,足足有545页。它的特点就是规定得非常细致,每一个语法行为、每一个函数的实现都做了详尽的清晰的描述。基本上,编译器作者只要把每一步翻译成代码就可以了。这很大程度上,保证了所有ES6实现都有一致的行为。
|
||||
这个规格文件相当庞大,一共有 26 章,A4 打印的话,足足有 545 页。它的特点就是规定得非常细致,每一个语法行为、每一个函数的实现都做了详尽的清晰的描述。基本上,编译器作者只要把每一步翻译成代码就可以了。这很大程度上,保证了所有 ES6 实现都有一致的行为。
|
||||
|
||||
ECMAScript 6规格的26章之中,第1章到第3章是对文件本身的介绍,与语言关系不大。第4章是对这门语言总体设计的描述,有兴趣的读者可以读一下。第5章到第8章是语言宏观层面的描述。第5章是规格的名词解释和写法的介绍,第6章介绍数据类型,第7章介绍语言内部用到的抽象操作,第8章介绍代码如何运行。第9章到第26章介绍具体的语法。
|
||||
ECMAScript 6 规格的 26 章之中,第 1 章到第 3 章是对文件本身的介绍,与语言关系不大。第 4 章是对这门语言总体设计的描述,有兴趣的读者可以读一下。第 5 章到第 8 章是语言宏观层面的描述。第 5 章是规格的名词解释和写法的介绍,第 6 章介绍数据类型,第 7 章介绍语言内部用到的抽象操作,第 8 章介绍代码如何运行。第 9 章到第 26 章介绍具体的语法。
|
||||
|
||||
对于一般用户来说,除了第4章,其他章节都涉及某一方面的细节,不用通读,只要在用到的时候,查阅相关章节即可。下面通过一些例子,介绍如何使用这份规格。
|
||||
对于一般用户来说,除了第 4 章,其他章节都涉及某一方面的细节,不用通读,只要在用到的时候,查阅相关章节即可。下面通过一些例子,介绍如何使用这份规格。
|
||||
|
||||
## 相等运算符
|
||||
|
||||
@ -28,7 +28,7 @@ ECMAScript 6规格的26章之中,第1章到第3章是对文件本身的介绍
|
||||
0 == null
|
||||
```
|
||||
|
||||
如果你不确定答案,或者想知道语言内部怎么处理,就可以去查看规格,[7.2.12小节](http://www.ecma-international.org/ecma-262/6.0/#sec-7.2.12)是对相等运算符(`==`)的描述。
|
||||
如果你不确定答案,或者想知道语言内部怎么处理,就可以去查看规格,[7.2.12 小节](http://www.ecma-international.org/ecma-262/6.0/#sec-7.2.12)是对相等运算符(`==`)的描述。
|
||||
|
||||
规格对每一种语法行为的描述,都分成两部分:先是总体的行为描述,然后是实现的算法细节。相等运算符的总体描述,只有一句话。
|
||||
|
||||
@ -40,23 +40,23 @@ ECMAScript 6规格的26章之中,第1章到第3章是对文件本身的介绍
|
||||
|
||||
> 1. ReturnIfAbrupt(x).
|
||||
> 1. ReturnIfAbrupt(y).
|
||||
> 1. If `Type(x)` is the same as `Type(y)`, then
|
||||
> Return the result of performing Strict Equality Comparison `x === y`.
|
||||
> 1. If `Type(x)` is the same as `Type(y)`, then\
|
||||
> Return the result of performing Strict Equality Comparison `x === y`.
|
||||
> 1. If `x` is `null` and `y` is `undefined`, return `true`.
|
||||
> 1. If `x` is `undefined` and `y` is `null`, return `true`.
|
||||
> 1. If `Type(x)` is Number and `Type(y)` is String,
|
||||
> return the result of the comparison `x == ToNumber(y)`.
|
||||
> 1. If `Type(x)` is String and `Type(y)` is Number,
|
||||
> return the result of the comparison `ToNumber(x) == y`.
|
||||
> 1. If `Type(x)` is Number and `Type(y)` is String,\
|
||||
> return the result of the comparison `x == ToNumber(y)`.
|
||||
> 1. If `Type(x)` is String and `Type(y)` is Number,\
|
||||
> return the result of the comparison `ToNumber(x) == y`.
|
||||
> 1. If `Type(x)` is Boolean, return the result of the comparison `ToNumber(x) == y`.
|
||||
> 1. If `Type(y)` is Boolean, return the result of the comparison `x == ToNumber(y)`.
|
||||
> 1. If `Type(x)` is either String, Number, or Symbol and `Type(y)` is Object, then
|
||||
> return the result of the comparison `x == ToPrimitive(y)`.
|
||||
> 1. If `Type(x)` is Object and `Type(y)` is either String, Number, or Symbol, then
|
||||
> return the result of the comparison `ToPrimitive(x) == y`.
|
||||
> 1. If `Type(x)` is either String, Number, or Symbol and `Type(y)` is Object, then\
|
||||
> return the result of the comparison `x == ToPrimitive(y)`.
|
||||
> 1. If `Type(x)` is Object and `Type(y)` is either String, Number, or Symbol, then\
|
||||
> return the result of the comparison `ToPrimitive(x) == y`.
|
||||
> 1. Return `false`.
|
||||
|
||||
上面这段算法,一共有12步,翻译如下。
|
||||
上面这段算法,一共有 12 步,翻译如下。
|
||||
|
||||
> 1. 如果`x`不是正常值(比如抛出一个错误),中断执行。
|
||||
> 1. 如果`y`不是正常值,中断执行。
|
||||
@ -71,7 +71,7 @@ ECMAScript 6规格的26章之中,第1章到第3章是对文件本身的介绍
|
||||
> 1. 如果`Type(x)`是对象,`Type(y)`是字符串或数值或`Symbol`值,返回`ToPrimitive(x) == y`的结果。
|
||||
> 1. 返回`false`。
|
||||
|
||||
由于`0`的类型是数值,`null`的类型是Null(这是规格[4.3.13小节](http://www.ecma-international.org/ecma-262/6.0/#sec-4.3.13)的规定,是内部Type运算的结果,跟`typeof`运算符无关)。因此上面的前11步都得不到结果,要到第12步才能得到`false`。
|
||||
由于`0`的类型是数值,`null`的类型是 Null(这是规格[4.3.13 小节](http://www.ecma-international.org/ecma-262/6.0/#sec-4.3.13)的规定,是内部 Type 运算的结果,跟`typeof`运算符无关)。因此上面的前 11 步都得不到结果,要到第 12 步才能得到`false`。
|
||||
|
||||
```javascript
|
||||
0 == null // false
|
||||
@ -94,7 +94,7 @@ a2[0] // undefined
|
||||
a1[0] === a2[0] // true
|
||||
```
|
||||
|
||||
上面代码中,数组`a1`的成员是三个`undefined`,数组`a2`的成员是三个空位。这两个数组很相似,长度都是3,每个位置的成员读取出来都是`undefined`。
|
||||
上面代码中,数组`a1`的成员是三个`undefined`,数组`a2`的成员是三个空位。这两个数组很相似,长度都是 3,每个位置的成员读取出来都是`undefined`。
|
||||
|
||||
但是,它们实际上存在重大差异。
|
||||
|
||||
@ -116,23 +116,23 @@ a2.map(n => 1) // [, , ,]
|
||||
|
||||
为什么`a1`与`a2`成员的行为不一致?数组的成员是`undefined`或空位,到底有什么不同?
|
||||
|
||||
规格的[12.2.5小节《数组的初始化》](http://www.ecma-international.org/ecma-262/6.0/#sec-12.2.5)给出了答案。
|
||||
规格的[12.2.5 小节《数组的初始化》](http://www.ecma-international.org/ecma-262/6.0/#sec-12.2.5)给出了答案。
|
||||
|
||||
> “Array elements may be elided at the beginning, middle or end of the element list. Whenever a comma in the element list is not preceded by an AssignmentExpression (i.e., a comma at the beginning or after another comma), the missing array element contributes to the length of the Array and increases the index of subsequent elements. Elided array elements are not defined. If an element is elided at the end of an array, that element does not contribute to the length of the Array.”
|
||||
|
||||
翻译如下。
|
||||
|
||||
> "数组成员可以省略。只要逗号前面没有任何表达式,数组的`length`属性就会加1,并且相应增加其后成员的位置索引。被省略的成员不会被定义。如果被省略的成员是数组最后一个成员,则不会导致数组`length`属性增加。”
|
||||
> "数组成员可以省略。只要逗号前面没有任何表达式,数组的`length`属性就会加 1,并且相应增加其后成员的位置索引。被省略的成员不会被定义。如果被省略的成员是数组最后一个成员,则不会导致数组`length`属性增加。”
|
||||
|
||||
上面的规格说得很清楚,数组的空位会反映在`length`属性,也就是说空位有自己的位置,但是这个位置的值是未定义,即这个值是不存在的。如果一定要读取,结果就是`undefined`(因为`undefined`在JavaScript语言中表示不存在)。
|
||||
上面的规格说得很清楚,数组的空位会反映在`length`属性,也就是说空位有自己的位置,但是这个位置的值是未定义,即这个值是不存在的。如果一定要读取,结果就是`undefined`(因为`undefined`在 JavaScript 语言中表示不存在)。
|
||||
|
||||
这就解释了为什么`in`运算符、数组的`hasOwnProperty`方法、`Object.keys`方法,都取不到空位的属性名。因为这个属性名根本就不存在,规格里面没说要为空位分配属性名(位置索引),只说要为下一个元素的位置索引加1。
|
||||
这就解释了为什么`in`运算符、数组的`hasOwnProperty`方法、`Object.keys`方法,都取不到空位的属性名。因为这个属性名根本就不存在,规格里面没说要为空位分配属性名(位置索引),只说要为下一个元素的位置索引加 1。
|
||||
|
||||
至于为什么数组的`map`方法会跳过空位,请看下一节。
|
||||
|
||||
## 数组的map方法
|
||||
## 数组的 map 方法
|
||||
|
||||
规格的[22.1.3.15小节](http://www.ecma-international.org/ecma-262/6.0/#sec-22.1.3.15)定义了数组的`map`方法。该小节先是总体描述`map`方法的行为,里面没有提到数组空位。
|
||||
规格的[22.1.3.15 小节](http://www.ecma-international.org/ecma-262/6.0/#sec-22.1.3.15)定义了数组的`map`方法。该小节先是总体描述`map`方法的行为,里面没有提到数组空位。
|
||||
|
||||
后面的算法描述是这样的。
|
||||
|
||||
@ -145,18 +145,18 @@ a2.map(n => 1) // [, , ,]
|
||||
> 1. Let `A` be `ArraySpeciesCreate(O, len)`.
|
||||
> 1. `ReturnIfAbrupt(A)`.
|
||||
> 1. Let `k` be 0.
|
||||
> 1. Repeat, while `k` < `len`
|
||||
> a. Let `Pk` be `ToString(k)`.
|
||||
> b. Let `kPresent` be `HasProperty(O, Pk)`.
|
||||
> c. `ReturnIfAbrupt(kPresent)`.
|
||||
> d. If `kPresent` is `true`, then
|
||||
> d-1. Let `kValue` be `Get(O, Pk)`.
|
||||
> d-2. `ReturnIfAbrupt(kValue)`.
|
||||
> d-3. Let `mappedValue` be `Call(callbackfn, T, «kValue, k, O»)`.
|
||||
> d-4. `ReturnIfAbrupt(mappedValue)`.
|
||||
> d-5. Let `status` be `CreateDataPropertyOrThrow (A, Pk, mappedValue)`.
|
||||
> d-6. `ReturnIfAbrupt(status)`.
|
||||
> e. Increase `k` by 1.
|
||||
> 1. Repeat, while `k` < `len`\
|
||||
> a. Let `Pk` be `ToString(k)`.\
|
||||
> b. Let `kPresent` be `HasProperty(O, Pk)`.\
|
||||
> c. `ReturnIfAbrupt(kPresent)`.\
|
||||
> d. If `kPresent` is `true`, then\
|
||||
> d-1. Let `kValue` be `Get(O, Pk)`.\
|
||||
> d-2. `ReturnIfAbrupt(kValue)`.\
|
||||
> d-3. Let `mappedValue` be `Call(callbackfn, T, «kValue, k, O»)`.\
|
||||
> d-4. `ReturnIfAbrupt(mappedValue)`.\
|
||||
> d-5. Let `status` be `CreateDataPropertyOrThrow (A, Pk, mappedValue)`.\
|
||||
> d-6. `ReturnIfAbrupt(status)`.\
|
||||
> e. Increase `k` by 1.
|
||||
> 1. Return `A`.
|
||||
|
||||
翻译如下。
|
||||
@ -165,26 +165,26 @@ a2.map(n => 1) // [, , ,]
|
||||
> 1. 如果报错就返回
|
||||
> 1. 求出当前数组的`length`属性
|
||||
> 1. 如果报错就返回
|
||||
> 1. 如果map方法的参数`callbackfn`不可执行,就报错
|
||||
> 1. 如果map方法的参数之中,指定了`this`,就让`T`等于该参数,否则`T`为`undefined`
|
||||
> 1. 如果 map 方法的参数`callbackfn`不可执行,就报错
|
||||
> 1. 如果 map 方法的参数之中,指定了`this`,就让`T`等于该参数,否则`T`为`undefined`
|
||||
> 1. 生成一个新的数组`A`,跟当前数组的`length`属性保持一致
|
||||
> 1. 如果报错就返回
|
||||
> 1. 设定`k`等于0
|
||||
> 1. 只要`k`小于当前数组的`length`属性,就重复下面步骤
|
||||
> a. 设定`Pk`等于`ToString(k)`,即将`K`转为字符串
|
||||
> b. 设定`kPresent`等于`HasProperty(O, Pk)`,即求当前数组有没有指定属性
|
||||
> c. 如果报错就返回
|
||||
> d. 如果`kPresent`等于`true`,则进行下面步骤
|
||||
> d-1. 设定`kValue`等于`Get(O, Pk)`,取出当前数组的指定属性
|
||||
> d-2. 如果报错就返回
|
||||
> d-3. 设定`mappedValue`等于`Call(callbackfn, T, «kValue, k, O»)`,即执行回调函数
|
||||
> d-4. 如果报错就返回
|
||||
> d-5. 设定`status`等于`CreateDataPropertyOrThrow (A, Pk, mappedValue)`,即将回调函数的值放入`A`数组的指定位置
|
||||
> d-6. 如果报错就返回
|
||||
> e. `k`增加1
|
||||
> 1. 设定`k`等于 0
|
||||
> 1. 只要`k`小于当前数组的`length`属性,就重复下面步骤\
|
||||
> a. 设定`Pk`等于`ToString(k)`,即将`K`转为字符串\
|
||||
> b. 设定`kPresent`等于`HasProperty(O, Pk)`,即求当前数组有没有指定属性\
|
||||
> c. 如果报错就返回\
|
||||
> d. 如果`kPresent`等于`true`,则进行下面步骤\
|
||||
> d-1. 设定`kValue`等于`Get(O, Pk)`,取出当前数组的指定属性\
|
||||
> d-2. 如果报错就返回\
|
||||
> d-3. 设定`mappedValue`等于`Call(callbackfn, T, «kValue, k, O»)`,即执行回调函数\
|
||||
> d-4. 如果报错就返回\
|
||||
> d-5. 设定`status`等于`CreateDataPropertyOrThrow (A, Pk, mappedValue)`,即将回调函数的值放入`A`数组的指定位置\
|
||||
> d-6. 如果报错就返回\
|
||||
> e. `k`增加 1
|
||||
> 1. 返回`A`
|
||||
|
||||
仔细查看上面的算法,可以发现,当处理一个全是空位的数组时,前面步骤都没有问题。进入第10步的b时,`kpresent`会报错,因为空位对应的属性名,对于数组来说是不存在的,因此就会返回,不会进行后面的步骤。
|
||||
仔细查看上面的算法,可以发现,当处理一个全是空位的数组时,前面步骤都没有问题。进入第 10 步的 b 时,`kpresent`会报错,因为空位对应的属性名,对于数组来说是不存在的,因此就会返回,不会进行后面的步骤。
|
||||
|
||||
```javascript
|
||||
const arr = [, , ,];
|
||||
@ -196,7 +196,7 @@ arr.map(n => {
|
||||
|
||||
上面代码中,`arr`是一个全是空位的数组,`map`方法遍历成员时,发现是空位,就直接跳过,不会进入回调函数。因此,回调函数里面的`console.log`语句根本不会执行,整个`map`方法返回一个全是空位的新数组。
|
||||
|
||||
V8引擎对`map`方法的[实现](https://github.com/v8/v8/blob/44c44521ae11859478b42004f57ea93df52526ee/src/js/array.js#L1347)如下,可以看到跟规格的算法描述完全一致。
|
||||
V8 引擎对`map`方法的[实现](https://github.com/v8/v8/blob/44c44521ae11859478b42004f57ea93df52526ee/src/js/array.js#L1347)如下,可以看到跟规格的算法描述完全一致。
|
||||
|
||||
```javascript
|
||||
function ArrayMap(f, receiver) {
|
||||
|
@ -21,7 +21,7 @@ JavaScript 允许采用`\uxxxx`形式表示一个字符,其中`xxxx`表示字
|
||||
// " 7"
|
||||
```
|
||||
|
||||
上面代码表示,如果直接在`\u`后面跟上超过`0xFFFF`的数值(比如`\u20BB7`),JavaScript会理解成`\u20BB+7`。由于`\u20BB`是一个不可打印字符,所以只会显示一个空格,后面跟着一个`7`。
|
||||
上面代码表示,如果直接在`\u`后面跟上超过`0xFFFF`的数值(比如`\u20BB7`),JavaScript 会理解成`\u20BB+7`。由于`\u20BB`是一个不可打印字符,所以只会显示一个空格,后面跟着一个`7`。
|
||||
|
||||
ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
|
||||
|
||||
@ -41,7 +41,7 @@ hell\u{6F} // 123
|
||||
|
||||
上面代码中,最后一个例子表明,大括号表示法与四字节的 UTF-16 编码是等价的。
|
||||
|
||||
有了这种表示法之后,JavaScript 共有6种方法可以表示一个字符。
|
||||
有了这种表示法之后,JavaScript 共有 6 种方法可以表示一个字符。
|
||||
|
||||
```javascript
|
||||
'\z' === 'z' // true
|
||||
@ -53,7 +53,7 @@ hell\u{6F} // 123
|
||||
|
||||
## codePointAt()
|
||||
|
||||
JavaScript内部,字符以UTF-16的格式储存,每个字符固定为`2`个字节。对于那些需要`4`个字节储存的字符(Unicode码点大于`0xFFFF`的字符),JavaScript会认为它们是两个字符。
|
||||
JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为`2`个字节。对于那些需要`4`个字节储存的字符(Unicode 码点大于`0xFFFF`的字符),JavaScript 会认为它们是两个字符。
|
||||
|
||||
```javascript
|
||||
var s = "𠮷";
|
||||
@ -65,9 +65,9 @@ s.charCodeAt(0) // 55362
|
||||
s.charCodeAt(1) // 57271
|
||||
```
|
||||
|
||||
上面代码中,汉字“𠮷”(注意,这个字不是“吉祥”的“吉”)的码点是`0x20BB7`,UTF-16编码为`0xD842 0xDFB7`(十进制为`55362 57271`),需要`4`个字节储存。对于这种`4`个字节的字符,JavaScript不能正确处理,字符串长度会误判为`2`,而且`charAt`方法无法读取整个字符,`charCodeAt`方法只能分别返回前两个字节和后两个字节的值。
|
||||
上面代码中,汉字“𠮷”(注意,这个字不是“吉祥”的“吉”)的码点是`0x20BB7`,UTF-16 编码为`0xD842 0xDFB7`(十进制为`55362 57271`),需要`4`个字节储存。对于这种`4`个字节的字符,JavaScript 不能正确处理,字符串长度会误判为`2`,而且`charAt`方法无法读取整个字符,`charCodeAt`方法只能分别返回前两个字节和后两个字节的值。
|
||||
|
||||
ES6提供了`codePointAt`方法,能够正确处理4个字节储存的字符,返回一个字符的码点。
|
||||
ES6 提供了`codePointAt`方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点。
|
||||
|
||||
```javascript
|
||||
let s = '𠮷a';
|
||||
@ -78,9 +78,9 @@ s.codePointAt(1) // 57271
|
||||
s.codePointAt(2) // 97
|
||||
```
|
||||
|
||||
`codePointAt`方法的参数,是字符在字符串中的位置(从0开始)。上面代码中,JavaScript将“𠮷a”视为三个字符,codePointAt方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点134071(即十六进制的`20BB7`)。在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,`codePointAt`方法的结果与`charCodeAt`方法相同。
|
||||
`codePointAt`方法的参数,是字符在字符串中的位置(从 0 开始)。上面代码中,JavaScript 将“𠮷a”视为三个字符,codePointAt 方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点 134071(即十六进制的`20BB7`)。在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,`codePointAt`方法的结果与`charCodeAt`方法相同。
|
||||
|
||||
总之,`codePointAt`方法会正确返回32位的UTF-16字符的码点。对于那些两个字节储存的常规字符,它的返回结果与`charCodeAt`方法相同。
|
||||
总之,`codePointAt`方法会正确返回 32 位的 UTF-16 字符的码点。对于那些两个字节储存的常规字符,它的返回结果与`charCodeAt`方法相同。
|
||||
|
||||
`codePointAt`方法返回的是码点的十进制值,如果想要十六进制的值,可以使用`toString`方法转换一下。
|
||||
|
||||
@ -91,7 +91,7 @@ s.codePointAt(0).toString(16) // "20bb7"
|
||||
s.codePointAt(2).toString(16) // "61"
|
||||
```
|
||||
|
||||
你可能注意到了,`codePointAt`方法的参数,仍然是不正确的。比如,上面代码中,字符`a`在字符串`s`的正确位置序号应该是1,但是必须向`codePointAt`方法传入2。解决这个问题的一个办法是使用`for...of`循环,因为它会正确识别32位的UTF-16字符。
|
||||
你可能注意到了,`codePointAt`方法的参数,仍然是不正确的。比如,上面代码中,字符`a`在字符串`s`的正确位置序号应该是 1,但是必须向`codePointAt`方法传入 2。解决这个问题的一个办法是使用`for...of`循环,因为它会正确识别 32 位的 UTF-16 字符。
|
||||
|
||||
```javascript
|
||||
let s = '𠮷a';
|
||||
@ -115,7 +115,7 @@ is32Bit("a") // false
|
||||
|
||||
## String.fromCodePoint()
|
||||
|
||||
ES5提供`String.fromCharCode`方法,用于从码点返回对应字符,但是这个方法不能识别32位的UTF-16字符(Unicode编号大于`0xFFFF`)。
|
||||
ES5 提供`String.fromCharCode`方法,用于从码点返回对应字符,但是这个方法不能识别 32 位的 UTF-16 字符(Unicode 编号大于`0xFFFF`)。
|
||||
|
||||
```javascript
|
||||
String.fromCharCode(0x20BB7)
|
||||
@ -124,7 +124,7 @@ String.fromCharCode(0x20BB7)
|
||||
|
||||
上面代码中,`String.fromCharCode`不能识别大于`0xFFFF`的码点,所以`0x20BB7`就发生了溢出,最高位`2`被舍弃了,最后返回码点`U+0BB7`对应的字符,而不是码点`U+20BB7`对应的字符。
|
||||
|
||||
ES6提供了`String.fromCodePoint`方法,可以识别大于`0xFFFF`的字符,弥补了`String.fromCharCode`方法的不足。在作用上,正好与`codePointAt`方法相反。
|
||||
ES6 提供了`String.fromCodePoint`方法,可以识别大于`0xFFFF`的字符,弥补了`String.fromCharCode`方法的不足。在作用上,正好与`codePointAt`方法相反。
|
||||
|
||||
```javascript
|
||||
String.fromCodePoint(0x20BB7)
|
||||
@ -139,7 +139,7 @@ String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
|
||||
|
||||
## 字符串的遍历器接口
|
||||
|
||||
ES6为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被`for...of`循环遍历。
|
||||
ES6 为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被`for...of`循环遍历。
|
||||
|
||||
```javascript
|
||||
for (let codePoint of 'foo') {
|
||||
@ -178,7 +178,7 @@ ES5 对字符串对象提供`charAt`方法,返回字符串给定位置的字
|
||||
'𠮷'.charAt(0) // "\uD842"
|
||||
```
|
||||
|
||||
上面代码中,`charAt`方法返回的是UTF-16编码的第一个字节,实际上是无法显示的。
|
||||
上面代码中,`charAt`方法返回的是 UTF-16 编码的第一个字节,实际上是无法显示的。
|
||||
|
||||
目前,有一个提案,提出字符串实例的`at`方法,可以识别 Unicode 编号大于`0xFFFF`的字符,返回正确的字符。
|
||||
|
||||
@ -225,11 +225,11 @@ ES6 提供字符串实例的`normalize()`方法,用来将字符的不同表示
|
||||
|
||||
上面代码表示,`NFC`参数返回字符的合成形式,`NFD`参数返回字符的分解形式。
|
||||
|
||||
不过,`normalize`方法目前不能识别三个或三个以上字符的合成。这种情况下,还是只能使用正则表达式,通过Unicode编号区间判断。
|
||||
不过,`normalize`方法目前不能识别三个或三个以上字符的合成。这种情况下,还是只能使用正则表达式,通过 Unicode 编号区间判断。
|
||||
|
||||
## includes(), startsWith(), endsWith()
|
||||
|
||||
传统上,JavaScript只有`indexOf`方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。
|
||||
传统上,JavaScript 只有`indexOf`方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。
|
||||
|
||||
- **includes()**:返回布尔值,表示是否找到了参数字符串。
|
||||
- **startsWith()**:返回布尔值,表示参数字符串是否在原字符串的头部。
|
||||
@ -280,13 +280,13 @@ s.includes('Hello', 6) // false
|
||||
// RangeError
|
||||
```
|
||||
|
||||
但是,如果参数是0到-1之间的小数,则等同于0,这是因为会先进行取整运算。0到-1之间的小数,取整以后等于`-0`,`repeat`视同为0。
|
||||
但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于`-0`,`repeat`视同为 0。
|
||||
|
||||
```javascript
|
||||
'na'.repeat(-0.9) // ""
|
||||
```
|
||||
|
||||
参数`NaN`等同于0。
|
||||
参数`NaN`等同于 0。
|
||||
|
||||
```javascript
|
||||
'na'.repeat(NaN) // ""
|
||||
@ -334,7 +334,7 @@ ES2017 引入了字符串补全长度的功能。如果某个字符串不够指
|
||||
'x'.padEnd(4) // 'x '
|
||||
```
|
||||
|
||||
`padStart`的常见用途是为数值补全指定位数。下面代码生成10位的数值字符串。
|
||||
`padStart`的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。
|
||||
|
||||
```javascript
|
||||
'1'.padStart(10, '0') // "0000000001"
|
||||
@ -351,7 +351,7 @@ ES2017 引入了字符串补全长度的功能。如果某个字符串不够指
|
||||
|
||||
## 模板字符串
|
||||
|
||||
传统的JavaScript语言,输出模板通常是这样写的。
|
||||
传统的 JavaScript 语言,输出模板通常是这样写的。
|
||||
|
||||
```javascript
|
||||
$('#result').append(
|
||||
@ -362,7 +362,7 @@ $('#result').append(
|
||||
);
|
||||
```
|
||||
|
||||
上面这种写法相当繁琐不方便,ES6引入了模板字符串解决这个问题。
|
||||
上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。
|
||||
|
||||
```javascript
|
||||
$('#result').append(`
|
||||
@ -409,7 +409,6 @@ $('#list').html(`
|
||||
|
||||
上面代码中,所有模板字符串的空格和换行,都是被保留的,比如`<ul>`标签前面会有一个换行。如果你不想要这个换行,可以使用`trim`方法消除它。
|
||||
|
||||
|
||||
```javascript
|
||||
$('#list').html(`
|
||||
<ul>
|
||||
@ -436,7 +435,7 @@ function authorize(user, action) {
|
||||
}
|
||||
```
|
||||
|
||||
大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。
|
||||
大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。
|
||||
|
||||
```javascript
|
||||
let x = 1;
|
||||
@ -474,7 +473,7 @@ let msg = `Hello, ${place}`;
|
||||
// 报错
|
||||
```
|
||||
|
||||
由于模板字符串的大括号内部,就是执行JavaScript代码,因此如果大括号内部是一个字符串,将会原样输出。
|
||||
由于模板字符串的大括号内部,就是执行 JavaScript 代码,因此如果大括号内部是一个字符串,将会原样输出。
|
||||
|
||||
```javascript
|
||||
`Hello ${'World'}`
|
||||
@ -542,11 +541,11 @@ let template = `
|
||||
`;
|
||||
```
|
||||
|
||||
上面代码在模板字符串之中,放置了一个常规模板。该模板使用`<%...%>`放置JavaScript代码,使用`<%= ... %>`输出JavaScript表达式。
|
||||
上面代码在模板字符串之中,放置了一个常规模板。该模板使用`<%...%>`放置 JavaScript 代码,使用`<%= ... %>`输出 JavaScript 表达式。
|
||||
|
||||
怎么编译这个模板字符串呢?
|
||||
|
||||
一种思路是将其转换为JavaScript表达式字符串。
|
||||
一种思路是将其转换为 JavaScript 表达式字符串。
|
||||
|
||||
```javascript
|
||||
echo('<ul>');
|
||||
@ -785,7 +784,6 @@ message
|
||||
// <p><script>alert("abc")</script> has sent you a message.</p>
|
||||
```
|
||||
|
||||
|
||||
标签模板的另一个应用,就是多语言转换(国际化处理)。
|
||||
|
||||
```javascript
|
||||
@ -793,7 +791,7 @@ i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`
|
||||
// "欢迎访问xxx,您是第xxxx位访问者!"
|
||||
```
|
||||
|
||||
模板字符串本身并不能取代Mustache之类的模板库,因为没有条件判断和循环处理功能,但是通过标签函数,你可以自己添加这些功能。
|
||||
模板字符串本身并不能取代 Mustache 之类的模板库,因为没有条件判断和循环处理功能,但是通过标签函数,你可以自己添加这些功能。
|
||||
|
||||
```javascript
|
||||
// 下面的hashTemplate函数
|
||||
@ -807,7 +805,7 @@ let libraryHtml = hashTemplate`
|
||||
`;
|
||||
```
|
||||
|
||||
除此之外,你甚至可以使用标签模板,在JavaScript语言之中嵌入其他语言。
|
||||
除此之外,你甚至可以使用标签模板,在 JavaScript 语言之中嵌入其他语言。
|
||||
|
||||
```javascript
|
||||
jsx`
|
||||
@ -821,9 +819,9 @@ jsx`
|
||||
`
|
||||
```
|
||||
|
||||
上面的代码通过`jsx`函数,将一个DOM字符串转为React对象。你可以在Github找到`jsx`函数的[具体实现](https://gist.github.com/lygaret/a68220defa69174bdec5)。
|
||||
上面的代码通过`jsx`函数,将一个 DOM 字符串转为 React 对象。你可以在 Github 找到`jsx`函数的[具体实现](https://gist.github.com/lygaret/a68220defa69174bdec5)。
|
||||
|
||||
下面则是一个假想的例子,通过`java`函数,在JavaScript代码之中运行Java代码。
|
||||
下面则是一个假想的例子,通过`java`函数,在 JavaScript 代码之中运行 Java 代码。
|
||||
|
||||
```javascript
|
||||
java`
|
||||
@ -857,11 +855,11 @@ function tag(strings) {
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`tag`函数的第一个参数`strings`,有一个`raw`属性,也指向一个数组。该数组的成员与`strings`数组完全一致。比如,`strings`数组是`["First line\nSecond line"]`,那么`strings.raw`数组就是`["First line\\nSecond line"]`。两者唯一的区别,就是字符串里面的斜杠都被转义了。比如,strings.raw数组会将`\n`视为`\\`和`n`两个字符,而不是换行符。这是为了方便取得转义之前的原始模板而设计的。
|
||||
上面代码中,`tag`函数的第一个参数`strings`,有一个`raw`属性,也指向一个数组。该数组的成员与`strings`数组完全一致。比如,`strings`数组是`["First line\nSecond line"]`,那么`strings.raw`数组就是`["First line\\nSecond line"]`。两者唯一的区别,就是字符串里面的斜杠都被转义了。比如,strings.raw 数组会将`\n`视为`\\`和`n`两个字符,而不是换行符。这是为了方便取得转义之前的原始模板而设计的。
|
||||
|
||||
## String.raw()
|
||||
|
||||
ES6还为原生的String对象,提供了一个`raw`方法。
|
||||
ES6 还为原生的 String 对象,提供了一个`raw`方法。
|
||||
|
||||
`String.raw`方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。
|
||||
|
||||
@ -941,11 +939,10 @@ function tag(strs) {
|
||||
tag`\unicode and \u{55}`
|
||||
```
|
||||
|
||||
上面代码中,模板字符串原本是应该报错的,但是由于放松了对字符串转义的限制,所以不报错了,JavaScript引擎将第一个字符设置为`undefined`,但是`raw`属性依然可以得到原始字符串,因此`tag`函数还是可以对原字符串进行处理。
|
||||
上面代码中,模板字符串原本是应该报错的,但是由于放松了对字符串转义的限制,所以不报错了,JavaScript 引擎将第一个字符设置为`undefined`,但是`raw`属性依然可以得到原始字符串,因此`tag`函数还是可以对原字符串进行处理。
|
||||
|
||||
注意,这种对字符串转义的放松,只在标签模板解析字符串时生效,不是标签模板的场合,依然会报错。
|
||||
|
||||
```javascript
|
||||
let bad = `bad escape sequence: \unicode`; // 报错
|
||||
```
|
||||
|
||||
|
@ -234,7 +234,7 @@ for (i = 0; i < len; i++) {
|
||||
const itemsCopy = [...items];
|
||||
```
|
||||
|
||||
使用Array.from方法,将类似数组的对象转为数组。
|
||||
使用 Array.from 方法,将类似数组的对象转为数组。
|
||||
|
||||
```javascript
|
||||
const foo = document.querySelectorAll('.foo');
|
||||
@ -251,7 +251,7 @@ const nodes = Array.from(foo);
|
||||
})();
|
||||
```
|
||||
|
||||
那些需要使用函数表达式的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了this。
|
||||
那些需要使用函数表达式的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了 this。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
@ -268,7 +268,7 @@ const nodes = Array.from(foo);
|
||||
[1, 2, 3].map(x => x * x);
|
||||
```
|
||||
|
||||
箭头函数取代`Function.prototype.bind`,不应再用self/\_this/that绑定 this。
|
||||
箭头函数取代`Function.prototype.bind`,不应再用 self/\_this/that 绑定 this。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
@ -298,7 +298,7 @@ function divide(a, b, { option = false } = {}) {
|
||||
}
|
||||
```
|
||||
|
||||
不要在函数体内使用arguments变量,使用rest运算符(...)代替。因为rest运算符显式表明你想要获取参数,而且arguments是一个类似数组的对象,而rest运算符可以提供一个真正的数组。
|
||||
不要在函数体内使用 arguments 变量,使用 rest 运算符(...)代替。因为 rest 运算符显式表明你想要获取参数,而且 arguments 是一个类似数组的对象,而 rest 运算符可以提供一个真正的数组。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
@ -327,9 +327,9 @@ function handleThings(opts = {}) {
|
||||
}
|
||||
```
|
||||
|
||||
## Map结构
|
||||
## Map 结构
|
||||
|
||||
注意区分Object和Map,只有模拟现实世界的实体对象时,才使用Object。如果只是需要`key: value`的数据结构,使用Map结构。因为Map有内建的遍历机制。
|
||||
注意区分 Object 和 Map,只有模拟现实世界的实体对象时,才使用 Object。如果只是需要`key: value`的数据结构,使用 Map 结构。因为 Map 有内建的遍历机制。
|
||||
|
||||
```javascript
|
||||
let map = new Map(arr);
|
||||
@ -349,7 +349,7 @@ for (let item of map.entries()) {
|
||||
|
||||
## Class
|
||||
|
||||
总是用Class,取代需要prototype的操作。因为Class的写法更简洁,更易于理解。
|
||||
总是用 Class,取代需要 prototype 的操作。因为 Class 的写法更简洁,更易于理解。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
@ -398,7 +398,7 @@ class PeekableQueue extends Queue {
|
||||
|
||||
## 模块
|
||||
|
||||
首先,Module语法是JavaScript模块的标准写法,坚持使用这种写法。使用`import`取代`require`。
|
||||
首先,Module 语法是 JavaScript 模块的标准写法,坚持使用这种写法。使用`import`取代`require`。
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
@ -468,7 +468,7 @@ const StyleGuide = {
|
||||
export default StyleGuide;
|
||||
```
|
||||
|
||||
## ESLint的使用
|
||||
## ESLint 的使用
|
||||
|
||||
ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。
|
||||
|
||||
@ -485,7 +485,7 @@ $ npm i -g eslint-config-airbnb
|
||||
$ npm i -g eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
|
||||
```
|
||||
|
||||
最后,在项目的根目录下新建一个`.eslintrc`文件,配置ESLint。
|
||||
最后,在项目的根目录下新建一个`.eslintrc`文件,配置 ESLint。
|
||||
|
||||
```javascript
|
||||
{
|
||||
@ -522,4 +522,4 @@ index.js
|
||||
✖ 5 problems (5 errors, 0 warnings)
|
||||
```
|
||||
|
||||
上面代码说明,原文件有五个错误,其中两个是不应该使用`var`命令,而要使用`let`或`const`;一个是定义了变量,却没有使用;另外两个是行首缩进为4个空格,而不是规定的2个空格。
|
||||
上面代码说明,原文件有五个错误,其中两个是不应该使用`var`命令,而要使用`let`或`const`;一个是定义了变量,却没有使用;另外两个是行首缩进为 4 个空格,而不是规定的 2 个空格。
|
||||
|
@ -247,7 +247,7 @@ const shapeType = {
|
||||
};
|
||||
```
|
||||
|
||||
上面代码中,除了将`shapeType.triangle`的值设为一个Symbol,其他地方都不用修改。
|
||||
上面代码中,除了将`shapeType.triangle`的值设为一个 Symbol,其他地方都不用修改。
|
||||
|
||||
## 属性名的遍历
|
||||
|
||||
@ -293,7 +293,7 @@ Object.getOwnPropertySymbols(obj)
|
||||
|
||||
上面代码中,使用`Object.getOwnPropertyNames`方法得不到`Symbol`属性名,需要使用`Object.getOwnPropertySymbols`方法。
|
||||
|
||||
另一个新的API,`Reflect.ownKeys`方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
|
||||
另一个新的 API,`Reflect.ownKeys`方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
|
||||
|
||||
```javascript
|
||||
let obj = {
|
||||
@ -341,7 +341,7 @@ Object.getOwnPropertySymbols(x) // [Symbol(size)]
|
||||
|
||||
## Symbol.for(),Symbol.keyFor()
|
||||
|
||||
有时,我们希望重新使用同一个 Symbol 值,`Symbol.for`方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。
|
||||
有时,我们希望重新使用同一个 Symbol 值,`Symbol.for`方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。
|
||||
|
||||
```javascript
|
||||
let s1 = Symbol.for('foo');
|
||||
@ -352,7 +352,7 @@ s1 === s2 // true
|
||||
|
||||
上面代码中,`s1`和`s2`都是 Symbol 值,但是它们都是同样参数的`Symbol.for`方法生成的,所以实际上是同一个值。
|
||||
|
||||
`Symbol.for()`与`Symbol()`这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。`Symbol.for()`不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的`key`是否已经存在,如果不存在才会新建一个值。比如,如果你调用`Symbol.for("cat")`30次,每次都会返回同一个 Symbol 值,但是调用`Symbol("cat")`30次,会返回30个不同的 Symbol 值。
|
||||
`Symbol.for()`与`Symbol()`这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。`Symbol.for()`不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的`key`是否已经存在,如果不存在才会新建一个值。比如,如果你调用`Symbol.for("cat")`30 次,每次都会返回同一个 Symbol 值,但是调用`Symbol("cat")`30 次,会返回 30 个不同的 Symbol 值。
|
||||
|
||||
```javascript
|
||||
Symbol.for("bar") === Symbol.for("bar")
|
||||
@ -391,9 +391,9 @@ iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')
|
||||
|
||||
## 实例:模块的 Singleton 模式
|
||||
|
||||
Singleton模式指的是调用一个类,任何时候返回的都是同一个实例。
|
||||
Singleton 模式指的是调用一个类,任何时候返回的都是同一个实例。
|
||||
|
||||
对于Node来说,模块文件可以看成是一个类。怎么保证每次执行这个模块文件,返回的都是同一个实例呢?
|
||||
对于 Node 来说,模块文件可以看成是一个类。怎么保证每次执行这个模块文件,返回的都是同一个实例呢?
|
||||
|
||||
很容易想到,可以把实例放到顶层对象`global`。
|
||||
|
||||
@ -428,7 +428,7 @@ global._foo = 123;
|
||||
|
||||
上面的代码,会使得别的脚本加载`mod.js`都失真。
|
||||
|
||||
为了防止这种情况出现,我们就可以使用Symbol。
|
||||
为了防止这种情况出现,我们就可以使用 Symbol。
|
||||
|
||||
```javascript
|
||||
// mod.js
|
||||
@ -461,11 +461,11 @@ const FOO_KEY = Symbol('foo');
|
||||
// 后面代码相同 ……
|
||||
```
|
||||
|
||||
上面代码将导致其他脚本都无法引用`FOO_KEY`。但这样也有一个问题,就是如果多次执行这个脚本,每次得到的`FOO_KEY`都是不一样的。虽然Node会将脚本的执行结果缓存,一般情况下,不会多次执行同一个脚本,但是用户可以手动清除缓存,所以也不是完全可靠。
|
||||
上面代码将导致其他脚本都无法引用`FOO_KEY`。但这样也有一个问题,就是如果多次执行这个脚本,每次得到的`FOO_KEY`都是不一样的。虽然 Node 会将脚本的执行结果缓存,一般情况下,不会多次执行同一个脚本,但是用户可以手动清除缓存,所以也不是完全可靠。
|
||||
|
||||
## 内置的Symbol值
|
||||
## 内置的 Symbol 值
|
||||
|
||||
除了定义自己使用的Symbol值以外,ES6还提供了11个内置的Symbol值,指向语言内部使用的方法。
|
||||
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
|
||||
|
||||
### Symbol.hasInstance
|
||||
|
||||
@ -712,7 +712,7 @@ myIterable[Symbol.iterator] = function* () {
|
||||
[...myIterable] // [1, 2, 3]
|
||||
```
|
||||
|
||||
对象进行`for...of`循环时,会调用`Symbol.iterator`方法,返回该对象的默认遍历器,详细介绍参见《Iterator和for...of循环》一章。
|
||||
对象进行`for...of`循环时,会调用`Symbol.iterator`方法,返回该对象的默认遍历器,详细介绍参见《Iterator 和 for...of 循环》一章。
|
||||
|
||||
```javascript
|
||||
class Collection {
|
||||
@ -787,11 +787,11 @@ let x = new Collection();
|
||||
Object.prototype.toString.call(x) // "[object xxx]"
|
||||
```
|
||||
|
||||
ES6新增内置对象的`Symbol.toStringTag`属性值如下。
|
||||
ES6 新增内置对象的`Symbol.toStringTag`属性值如下。
|
||||
|
||||
- `JSON[Symbol.toStringTag]`:'JSON'
|
||||
- `Math[Symbol.toStringTag]`:'Math'
|
||||
- Module对象`M[Symbol.toStringTag]`:'Module'
|
||||
- Module 对象`M[Symbol.toStringTag]`:'Module'
|
||||
- `ArrayBuffer.prototype[Symbol.toStringTag]`:'ArrayBuffer'
|
||||
- `DataView.prototype[Symbol.toStringTag]`:'DataView'
|
||||
- `Map.prototype[Symbol.toStringTag]`:'Map'
|
||||
@ -827,7 +827,7 @@ Object.keys(Array.prototype[Symbol.unscopables])
|
||||
// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'includes', 'keys']
|
||||
```
|
||||
|
||||
上面代码说明,数组有7个属性,会被`with`命令排除。
|
||||
上面代码说明,数组有 7 个属性,会被`with`命令排除。
|
||||
|
||||
```javascript
|
||||
// 没有 unscopables 时
|
||||
|
Loading…
x
Reference in New Issue
Block a user