diff --git a/.github/workflows/wangdoc.yml b/.github/workflows/wangdoc.yml
index 9601c5e..d879bfe 100644
--- a/.github/workflows/wangdoc.yml
+++ b/.github/workflows/wangdoc.yml
@@ -10,29 +10,28 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup Node.js
- uses: actions/setup-node@main
+ uses: actions/setup-node@v4
with:
- node-version: '14'
+ node-version: 'latest'
- name: Install dependencies
run: npm install
- name: Build pages
run: npm run build
- name: Deploy to website
- uses: JamesIves/github-pages-deploy-action@3.7.1
+ uses: JamesIves/github-pages-deploy-action@v4
with:
- GIT_CONFIG_NAME: wangdoc-bot
- GIT_CONFIG_EMAIL: yifeng.ruan@gmail.com
- REPOSITORY_NAME: wangdoc/website
- ACCESS_TOKEN: ${{ secrets.WANGDOC_BOT_TOKEN }}
- BASE_BRANCH: master
- BRANCH: master # The branch the action should deploy to.
- FOLDER: dist # The folder the action should deploy.
- TARGET_FOLDER: dist/es6
- CLEAN: true # Automatically remove deleted files from the deploy branch
- COMMIT_MESSAGE: update from ES6 tutorial
+ git-config-name: wangdoc-bot
+ git-config-email: yifeng.ruan@gmail.com
+ repository-name: wangdoc/website
+ token: ${{ secrets.WANGDOC_BOT_TOKEN }}
+ branch: master
+ folder: dist # The folder the action should deploy.
+ target-folder: dist/es6
+ clean: true # Automatically remove deleted files from the deploy branch
+ commit-message: update from ES6 tutorial
diff --git a/.gitignore b/.gitignore
index b947077..320c107 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
node_modules/
dist/
+package-lock.json
diff --git a/README.md b/README.md
index 64a0ea4..5e058d4 100644
--- a/README.md
+++ b/README.md
@@ -4,13 +4,11 @@
本书覆盖 ES6 与上一个版本 ES5 的所有不同之处,对涉及的语法知识给予详细介绍,并给出大量简洁易懂的示例代码。
-本书为中级难度,适合已经掌握 ES5 的读者,用来了解这门语言的最新发展;也可当作参考手册,查寻新增的语法点。如果你是 JavaScript 语言的初学者,建议先学完[《JavaScript 语言入门教程》](https://wangdoc.com/javascript/),再来看本书。
+本书为中级难度,适合已经掌握 ES5 的读者,用来了解这门语言的最新发展;也可当作参考手册,查寻新增的语法点。如果你是 JavaScript 语言的初学者,建议先学完[《JavaScript 语言教程》](https://wangdoc.com/javascript/),再来看本书。
-全书已由电子工业出版社出版,2017年9月推出了第三版,书名为《ES6 标准入门》。纸版是基于网站内容排版印刷的。
+全书已由电子工业出版社出版,2017年9月推出了第三版,书名为《ES6 标准入门》。纸版内容截止到出版时,网站内容一直在修订。
- [淘宝](https://s.taobao.com/search?q=ES6%E6%A0%87%E5%87%86%E5%85%A5%E9%97%A8+%E7%AC%AC3%E7%89%88)
- [京东](https://search.jd.com/Search?keyword=ES6%E6%A0%87%E5%87%86%E5%85%A5%E9%97%A8%20%E7%AC%AC3%E7%89%88&enc=utf-8&wq=ES6%E6%A0%87%E5%87%86%E5%85%A5%E9%97%A8%20%E7%AC%AC3%E7%89%88)
- [当当](http://product.dangdang.com/25156888.html)
-- [亚马逊](https://www.amazon.cn/ES6%E6%A0%87%E5%87%86%E5%85%A5%E9%97%A8-%E9%98%AE%E4%B8%80%E5%B3%B0/dp/B0755547ZZ)
-- [China-pub](http://product.china-pub.com/6504650)
diff --git a/docs/array.md b/docs/array.md
index e698309..7a3cda6 100644
--- a/docs/array.md
+++ b/docs/array.md
@@ -73,9 +73,9 @@ console.log(...[1, 2])
上面三种情况,扩展运算符都放在圆括号里面,但是前两种情况会报错,因为扩展运算符所在的括号不是函数调用。
-### 替代函数的 apply 方法
+### 替代函数的 apply() 方法
-由于扩展运算符可以展开数组,所以不再需要`apply`方法,将数组转为函数的参数了。
+由于扩展运算符可以展开数组,所以不再需要`apply()`方法将数组转为函数的参数了。
```javascript
// ES5 的写法
@@ -85,7 +85,7 @@ function f(x, y, z) {
var args = [0, 1, 2];
f.apply(null, args);
-// ES6的写法
+// ES6 的写法
function f(x, y, z) {
// ...
}
@@ -93,7 +93,7 @@ let args = [0, 1, 2];
f(...args);
```
-下面是扩展运算符取代`apply`方法的一个实际的例子,应用`Math.max`方法,简化求出一个数组最大元素的写法。
+下面是扩展运算符取代`apply()`方法的一个实际的例子,应用`Math.max()`方法,简化求出一个数组最大元素的写法。
```javascript
// ES5 的写法
@@ -106,12 +106,12 @@ Math.max(...[14, 3, 77])
Math.max(14, 3, 77);
```
-上面代码中,由于 JavaScript 不提供求数组最大元素的函数,所以只能套用`Math.max`函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用`Math.max`了。
+上面代码中,由于 JavaScript 不提供求数组最大元素的函数,所以只能套用`Math.max()`函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用`Math.max()`了。
-另一个例子是通过`push`函数,将一个数组添加到另一个数组的尾部。
+另一个例子是通过`push()`函数,将一个数组添加到另一个数组的尾部。
```javascript
-// ES5的 写法
+// ES5 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
@@ -122,13 +122,14 @@ let arr2 = [3, 4, 5];
arr1.push(...arr2);
```
-上面代码的 ES5 写法中,`push`方法的参数不能是数组,所以只好通过`apply`方法变通使用`push`方法。有了扩展运算符,就可以直接将数组传入`push`方法。
+上面代码的 ES5 写法中,`push()`方法的参数不能是数组,所以只好通过`apply()`方法变通使用`push()`方法。有了扩展运算符,就可以直接将数组传入`push()`方法。
下面是另外一个例子。
```javascript
// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
+
// ES6
new Date(...[2015, 1, 1]);
```
@@ -213,6 +214,7 @@ a4[0] === a1[0] // true
```javascript
// ES5
a = list[0], rest = list.slice(1)
+
// ES6
[a, ...rest] = list
```
@@ -281,7 +283,7 @@ str.split('').reverse().join('')
// 'y\uD83D\uDE80x'
```
-上面代码中,如果不用扩展运算符,字符串的`reverse`操作就不正确。
+上面代码中,如果不用扩展运算符,字符串的`reverse()`操作就不正确。
**(5)实现了 Iterator 接口的对象**
@@ -292,7 +294,7 @@ let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
```
-上面代码中,`querySelectorAll`方法返回的是一个`NodeList`对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于`NodeList`对象实现了 Iterator 。
+上面代码中,`querySelectorAll()`方法返回的是一个`NodeList`对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于`NodeList`对象实现了 Iterator。
```javascript
Number.prototype[Symbol.iterator] = function*() {
@@ -361,9 +363,9 @@ 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`将它转为真正的数组。
+下面是一个类似数组的对象,`Array.from()`将它转为真正的数组。
```javascript
let arrayLike = {
@@ -373,32 +375,32 @@ let arrayLike = {
length: 3
};
-// ES5的写法
+// ES5 的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
-// ES6的写法
+// ES6 的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
```
-实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的`arguments`对象。`Array.from`都可以将它们转为真正的数组。
+实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的`arguments`对象。`Array.from()`都可以将它们转为真正的数组。
```javascript
-// NodeList对象
+// NodeList 对象
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
return p.textContent.length > 100;
});
-// arguments对象
+// arguments 对象
function foo() {
var args = Array.from(arguments);
// ...
}
```
-上面代码中,`querySelectorAll`方法返回的是一个类似数组的对象,可以将这个对象转为真正的数组,再使用`filter`方法。
+上面代码中,`querySelectorAll()`方法返回的是一个类似数组的对象,可以将这个对象转为真正的数组,再使用`filter()`方法。
-只要是部署了 Iterator 接口的数据结构,`Array.from`都能将其转为数组。
+只要是部署了 Iterator 接口的数据结构,`Array.from()`都能将其转为数组。
```javascript
Array.from('hello')
@@ -408,9 +410,9 @@ let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
```
-上面代码中,字符串和 Set 结构都具有 Iterator 接口,因此可以被`Array.from`转为真正的数组。
+上面代码中,字符串和 Set 结构都具有 Iterator 接口,因此可以被`Array.from()`转为真正的数组。
-如果参数是一个真正的数组,`Array.from`会返回一个一模一样的新数组。
+如果参数是一个真正的数组,`Array.from()`会返回一个一模一样的新数组。
```javascript
Array.from([1, 2, 3])
@@ -429,16 +431,16 @@ function foo() {
[...document.querySelectorAll('div')]
```
-扩展运算符背后调用的是遍历器接口(`Symbol.iterator`),如果一个对象没有部署这个接口,就无法转换。`Array.from`方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有`length`属性。因此,任何有`length`属性的对象,都可以通过`Array.from`方法转为数组,而此时扩展运算符就无法转换。
+扩展运算符背后调用的是遍历器接口(`Symbol.iterator`),如果一个对象没有部署这个接口,就无法转换。`Array.from()`方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有`length`属性。因此,任何有`length`属性的对象,都可以通过`Array.from()`方法转为数组,而此时扩展运算符就无法转换。
```javascript
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
```
-上面代码中,`Array.from`返回了一个具有三个成员的数组,每个位置的值都是`undefined`。扩展运算符转换不了这个对象。
+上面代码中,`Array.from()`返回了一个具有三个成员的数组,每个位置的值都是`undefined`。扩展运算符转换不了这个对象。
-对于还没有部署该方法的浏览器,可以用`Array.prototype.slice`方法替代。
+对于还没有部署该方法的浏览器,可以用`Array.prototype.slice()`方法替代。
```javascript
const toArray = (() =>
@@ -446,7 +448,7 @@ const toArray = (() =>
)();
```
-`Array.from`还可以接受第二个参数,作用类似于数组的`map`方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
+`Array.from()`还可以接受一个函数作为第二个参数,作用类似于数组的`map()`方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
```javascript
Array.from(arrayLike, x => x * x);
@@ -486,7 +488,7 @@ typesOf(null, [], NaN)
// ['object', 'object', 'number']
```
-如果`map`函数里面用到了`this`关键字,还可以传入`Array.from`的第三个参数,用来绑定`this`。
+如果`map()`函数里面用到了`this`关键字,还可以传入`Array.from()`的第三个参数,用来绑定`this`。
`Array.from()`可以将各种值转为真正的数组,并且还提供`map`功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数量众多的数组方法。
@@ -495,7 +497,7 @@ Array.from({ length: 2 }, () => 'jack')
// ['jack', 'jack']
```
-上面代码中,`Array.from`的第一个参数指定了第二个参数运行的次数。这种特性可以让该方法的用法变得非常灵活。
+上面代码中,`Array.from()`的第一个参数指定了第二个参数运行的次数。这种特性可以让该方法的用法变得非常灵活。
`Array.from()`的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种 Unicode 字符,可以避免 JavaScript 将大于`\uFFFF`的 Unicode 字符,算作两个字符的 bug。
@@ -544,7 +546,7 @@ function ArrayOf(){
}
```
-## 数组实例的 copyWithin()
+## 实例方法:copyWithin()
数组实例的`copyWithin()`方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
@@ -593,9 +595,9 @@ i32a.copyWithin(0, 2);
// Int32Array [4, 2, 3, 4, 5]
```
-## 数组实例的 find() 和 findIndex()
+## 实例方法:find(),findIndex(),findLast(),findLastIndex()
-数组实例的`find`方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为`true`的成员,然后返回该成员。如果没有符合条件的成员,则返回`undefined`。
+数组实例的`find()`方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为`true`的成员,然后返回该成员。如果没有符合条件的成员,则返回`undefined`。
```javascript
[1, 4, -5, 10].find((n) => n < 0)
@@ -610,9 +612,9 @@ i32a.copyWithin(0, 2);
}) // 10
```
-上面代码中,`find`方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
+上面代码中,`find()`方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
-数组实例的`findIndex`方法的用法与`find`方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回`-1`。
+数组实例的`findIndex()`方法的用法与`find()`方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回`-1`。
```javascript
[1, 5, 10, 15].findIndex(function(value, index, arr) {
@@ -630,9 +632,9 @@ let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person); // 26
```
-上面的代码中,`find`函数接收了第二个参数`person`对象,回调函数中的`this`对象指向`person`对象。
+上面的代码中,`find()`函数接收了第二个参数`person`对象,回调函数中的`this`对象指向`person`对象。
-另外,这两个方法都可以发现`NaN`,弥补了数组的`indexOf`方法的不足。
+另外,这两个方法都可以发现`NaN`,弥补了数组的`indexOf()`方法的不足。
```javascript
[NaN].indexOf(NaN)
@@ -642,9 +644,25 @@ let person = {name: 'John', age: 20};
// 0
```
-上面代码中,`indexOf`方法无法识别数组的`NaN`成员,但是`findIndex`方法可以借助`Object.is`方法做到。
+上面代码中,`indexOf()`方法无法识别数组的`NaN`成员,但是`findIndex()`方法可以借助`Object.is()`方法做到。
-## 数组实例的 fill()
+`find()`和`findIndex()`都是从数组的0号位,依次向后检查。[ES2022](https://github.com/tc39/proposal-array-find-from-last) 新增了两个方法`findLast()`和`findLastIndex()`,从数组的最后一个成员开始,依次向前检查,其他都保持不变。
+
+```javascript
+const array = [
+ { value: 1 },
+ { value: 2 },
+ { value: 3 },
+ { value: 4 }
+];
+
+array.findLast(n => n.value % 2 === 1); // { value: 3 }
+array.findLastIndex(n => n.value % 2 === 1); // 2
+```
+
+上面示例中,`findLast()`和`findLastIndex()`从数组结尾开始,寻找第一个`value`属性为奇数的成员。结果,该成员是`{ value: 3 }`,位置是2号位。
+
+## 实例方法:fill()
`fill`方法使用给定值,填充一个数组。
@@ -681,7 +699,7 @@ arr
// [[5], [5], [5]]
```
-## 数组实例的 entries(),keys() 和 values()
+## 实例方法:entries(),keys() 和 values()
ES6 提供三个新的方法——`entries()`,`keys()`和`values()`——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用`for...of`循环进行遍历,唯一的区别是`keys()`是对键名的遍历、`values()`是对键值的遍历,`entries()`是对键值对的遍历。
@@ -715,7 +733,7 @@ console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']
```
-## 数组实例的 includes()
+## 实例方法:includes()
`Array.prototype.includes`方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的`includes`方法类似。ES2016 引入了该方法。
@@ -770,7 +788,7 @@ contains(['foo', 'bar'], 'baz'); // => false
- Map 结构的`has`方法,是用来查找键名的,比如`Map.prototype.has(key)`、`WeakMap.prototype.has(key)`、`Reflect.has(target, propertyKey)`。
- Set 结构的`has`方法,是用来查找值的,比如`Set.prototype.has(value)`、`WeakSet.prototype.has(value)`。
-## 数组实例的 flat(),flatMap()
+## 实例方法:flat(),flatMap()
数组的成员有时还是数组,`Array.prototype.flat()`用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
@@ -835,9 +853,120 @@ arr.flatMap(function callback(currentValue[, index[, array]]) {
`flatMap()`方法还可以有第二个参数,用来绑定遍历函数里面的`this`。
+## 实例方法:at()
+
+长久以来,JavaScript 不支持数组的负索引,如果要引用数组的最后一个成员,不能写成`arr[-1]`,只能使用`arr[arr.length - 1]`。
+
+这是因为方括号运算符`[]`在 JavaScript 语言里面,不仅用于数组,还用于对象。对于对象来说,方括号里面就是键名,比如`obj[1]`引用的是键名为字符串`1`的键,同理`obj[-1]`引用的是键名为字符串`-1`的键。由于 JavaScript 的数组是特殊的对象,所以方括号里面的负数无法再有其他语义了,也就是说,不可能添加新语法来支持负索引。
+
+为了解决这个问题,[ES2022](https://github.com/tc39/proposal-relative-indexing-method/) 为数组实例增加了`at()`方法,接受一个整数作为参数,返回对应位置的成员,并支持负索引。这个方法不仅可用于数组,也可用于字符串和类型数组(TypedArray)。
+
+```javascript
+const arr = [5, 12, 8, 130, 44];
+arr.at(2) // 8
+arr.at(-2) // 130
+```
+
+如果参数位置超出了数组范围,`at()`返回`undefined`。
+
+```javascript
+const sentence = 'This is a sample sentence';
+
+sentence.at(0); // 'T'
+sentence.at(-1); // 'e'
+
+sentence.at(-100) // undefined
+sentence.at(100) // undefined
+```
+
+## 实例方法:toReversed(),toSorted(),toSpliced(),with()
+
+很多数组的传统方法会改变原数组,比如`push()`、`pop()`、`shift()`、`unshift()`等等。数组只要调用了这些方法,它的值就变了。[ES2023](https://github.com/tc39/proposal-change-array-by-copy)引入了四个新方法,对数组进行操作时,不改变原数组,而返回一个原数组的拷贝。
+
+- `Array.prototype.toReversed() -> Array`
+- `Array.prototype.toSorted(compareFn) -> Array`
+- `Array.prototype.toSpliced(start, deleteCount, ...items) -> Array`
+- `Array.prototype.with(index, value) -> Array`
+
+它们分别对应数组的原有方法。
+
+- `toReversed()`对应`reverse()`,用来颠倒数组成员的位置。
+- `toSorted()`对应`sort()`,用来对数组成员排序。
+- `toSpliced()`对应`splice()`,用来在指定位置,删除指定数量的成员,并插入新成员。
+- `with(index, value)`对应`splice(index, 1, value)`,用来将指定位置的成员替换为新的值。
+
+上面是这四个新方法对应的原有方法,含义和用法完全一样,唯一不同的是不会改变原数组,而是返回原数组操作后的拷贝。
+
+下面是示例。
+
+```javascript
+const sequence = [1, 2, 3];
+sequence.toReversed() // [3, 2, 1]
+sequence // [1, 2, 3]
+
+const outOfOrder = [3, 1, 2];
+outOfOrder.toSorted() // [1, 2, 3]
+outOfOrder // [3, 1, 2]
+
+const array = [1, 2, 3, 4];
+array.toSpliced(1, 2, 5, 6, 7) // [1, 5, 6, 7, 4]
+array // [1, 2, 3, 4]
+
+const correctionNeeded = [1, 1, 3];
+correctionNeeded.with(1, 2) // [1, 2, 3]
+correctionNeeded // [1, 1, 3]
+```
+
+## 实例方法:group(),groupToMap()
+
+数组成员分组是一个常见需求,比如 SQL 有`GROUP BY`子句和函数式编程有 MapReduce 方法。现在有一个[提案](https://github.com/tc39/proposal-array-grouping),为 JavaScript 新增了数组实例方法`group()`和`groupToMap()`,它们可以根据分组函数的运行结果,将数组成员分组。
+
+`group()`的参数是一个分组函数,原数组的每个成员都会依次执行这个函数,确定自己是哪一个组。
+
+```javascript
+const array = [1, 2, 3, 4, 5];
+
+array.group((num, index, array) => {
+ return num % 2 === 0 ? 'even': 'odd';
+});
+// { odd: [1, 3, 5], even: [2, 4] }
+```
+
+`group()`的分组函数可以接受三个参数,依次是数组的当前成员、该成员的位置序号、原数组(上例是`num`、`index`和`array`)。分组函数的返回值应该是字符串(或者可以自动转为字符串),以作为分组后的组名。
+
+`group()`的返回值是一个对象,该对象的键名就是每一组的组名,即分组函数返回的每一个字符串(上例是`even`和`odd`);该对象的键值是一个数组,包括所有产生当前键名的原数组成员。
+
+下面是另一个例子。
+
+```javascript
+[6.1, 4.2, 6.3].group(Math.floor)
+// { '4': [4.2], '6': [6.1, 6.3] }
+```
+
+上面示例中,`Math.floor`作为分组函数,对原数组进行分组。它的返回值原本是数值,这时会自动转为字符串,作为分组的组名。原数组的成员根据分组函数的运行结果,进入对应的组。
+
+`group()`还可以接受一个对象,作为第二个参数。该对象会绑定分组函数(第一个参数)里面的`this`,不过如果分组函数是一个箭头函数,该对象无效,因为箭头函数内部的`this`是固化的。
+
+`groupToMap()`的作用和用法与`group()`完全一致,唯一的区别是返回值是一个 Map 结构,而不是对象。Map 结构的键名可以是各种值,所以不管分组函数返回什么值,都会直接作为组名(Map 结构的键名),不会强制转为字符串。这对于分组函数返回值是对象的情况,尤其有用。
+
+```javascript
+const array = [1, 2, 3, 4, 5];
+
+const odd = { odd: true };
+const even = { even: true };
+array.groupToMap((num, index, array) => {
+ return num % 2 === 0 ? even: odd;
+});
+// Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
+```
+
+上面示例返回的是一个 Map 结构,它的键名就是分组函数返回的两个对象`odd`和`even`。
+
+总之,按照字符串分组就使用`group()`,按照对象分组就使用`groupToMap()`。
+
## 数组的空位
-数组的空位指,数组的某一个位置没有任何值。比如,`Array`构造函数返回的数组都是空位。
+数组的空位指的是,数组的某一个位置没有任何值,比如`Array()`构造函数返回的数组都是空位。
```javascript
Array(3) // [, , ,]
@@ -845,7 +974,7 @@ Array(3) // [, , ,]
上面代码中,`Array(3)`返回一个具有 3 个空位的数组。
-注意,空位不是`undefined`,一个位置的值等于`undefined`,依然是有值的。空位是没有任何值,`in`运算符可以说明这一点。
+注意,空位不是`undefined`,某一个位置的值等于`undefined`,依然是有值的。空位是没有任何值,`in`运算符可以说明这一点。
```javascript
0 in [undefined, undefined, undefined] // true
@@ -888,7 +1017,7 @@ ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空
ES6 则是明确将空位转为`undefined`。
-`Array.from`方法会将数组的空位,转为`undefined`,也就是说,这个方法不会忽略空位。
+`Array.from()`方法会将数组的空位,转为`undefined`,也就是说,这个方法不会忽略空位。
```javascript
Array.from(['a',,'b'])
@@ -925,7 +1054,7 @@ for (let i of arr) {
// 1
```
-上面代码中,数组`arr`有两个空位,`for...of`并没有忽略它们。如果改成`map`方法遍历,空位是会跳过的。
+上面代码中,数组`arr`有两个空位,`for...of`并没有忽略它们。如果改成`map()`方法遍历,空位是会跳过的。
`entries()`、`keys()`、`values()`、`find()`和`findIndex()`会将空位处理成`undefined`。
diff --git a/docs/arraybuffer.md b/docs/arraybuffer.md
index 61325fd..8633709 100644
--- a/docs/arraybuffer.md
+++ b/docs/arraybuffer.md
@@ -16,7 +16,7 @@
简单说,`ArrayBuffer`对象代表原始的二进制数据,`TypedArray`视图用来读写简单类型的二进制数据,`DataView`视图用来读写复杂类型的二进制数据。
-`TypedArray`视图支持的数据类型一共有 9 种(`DataView`视图支持除`Uint8C`以外的其他 8 种)。
+`TypedArray`视图支持的数据类型一共有12种。
| 数据类型 | 字节长度 | 含义 | 对应的 C 语言类型 |
| -------- | -------- | -------------------------------- | ----------------- |
@@ -27,6 +27,9 @@
| Uint16 | 2 | 16 位不带符号整数 | unsigned short |
| Int32 | 4 | 32 位带符号整数 | int |
| Uint32 | 4 | 32 位不带符号的整数 | unsigned int |
+| BigInt64 | 8 | 64 位有符号整数 | |
+| BigUint64 | 8 | 64 位无符号整数 | |
+| Float16 | 2 | 16 位浮点数 | |
| Float32 | 4 | 32 位浮点数 | float |
| Float64 | 8 | 64 位浮点数 | double |
@@ -153,10 +156,13 @@ ArrayBuffer.isView(v) // true
- **`Uint16Array`**:16 位无符号整数,长度 2 个字节。
- **`Int32Array`**:32 位有符号整数,长度 4 个字节。
- **`Uint32Array`**:32 位无符号整数,长度 4 个字节。
+- **`BigInt64Array`**: 64 位有符号整数,长度 8 个字节。
+- **`BigUint64Array`**:64 位无符号整数,长度 8 个字节。
+- **`Float16Array`**: 16 位浮点数,长度 2 个字节。
- **`Float32Array`**:32 位浮点数,长度 4 个字节。
- **`Float64Array`**:64 位浮点数,长度 8 个字节。
-这 9 个构造函数生成的数组,统称为`TypedArray`视图。它们很像普通数组,都有`length`属性,都能用方括号运算符(`[]`)获取单个元素,所有数组的方法,在它们上面都能使用。普通数组与 TypedArray 数组的差异主要在以下方面。
+这12个构造函数生成的数组,统称为`TypedArray`视图。它们很像普通数组,都有`length`属性,都能用方括号运算符(`[]`)获取单个元素,所有数组的方法,在它们上面都能使用。普通数组与 TypedArray 数组的差异主要在以下方面。
- TypedArray 数组的所有成员,都是同一种类型。
- TypedArray 数组的成员是连续的,不会有空位。
@@ -165,7 +171,7 @@ ArrayBuffer.isView(v) // true
### 构造函数
-TypedArray 数组提供 9 种构造函数,用来生成相应类型的数组实例。
+TypedArray 数组提供12种构造函数,用来生成相应类型的数组实例。
构造函数有多种用法。
@@ -746,7 +752,7 @@ const dv = new DataView(buffer);
- `DataView.prototype.byteLength`:返回占据的内存字节长度
- `DataView.prototype.byteOffset`:返回当前视图从对应的 ArrayBuffer 对象的哪个字节开始
-`DataView`实例提供 8 个方法读取内存。
+`DataView`实例提供11个方法读取内存。
- **`getInt8`**:读取 1 个字节,返回一个 8 位整数。
- **`getUint8`**:读取 1 个字节,返回一个无符号的 8 位整数。
@@ -754,6 +760,9 @@ const dv = new DataView(buffer);
- **`getUint16`**:读取 2 个字节,返回一个无符号的 16 位整数。
- **`getInt32`**:读取 4 个字节,返回一个 32 位整数。
- **`getUint32`**:读取 4 个字节,返回一个无符号的 32 位整数。
+- **`getBigInt64`**:读取 8 个字节,返回一个 64 位整数。
+- **`getBigUint64`**:读取 8 个字节,返回一个无符号的 64 位整数。
+- **`getFloat16`**:读取 2 个字节,返回一个 16 位浮点数。
- **`getFloat32`**:读取 4 个字节,返回一个 32 位浮点数。
- **`getFloat64`**:读取 8 个字节,返回一个 64 位浮点数。
@@ -788,7 +797,7 @@ const v2 = dv.getUint16(3, false);
const v3 = dv.getUint16(3);
```
-DataView 视图提供 8 个方法写入内存。
+DataView 视图提供11个方法写入内存。
- **`setInt8`**:写入 1 个字节的 8 位整数。
- **`setUint8`**:写入 1 个字节的 8 位无符号整数。
@@ -796,6 +805,9 @@ DataView 视图提供 8 个方法写入内存。
- **`setUint16`**:写入 2 个字节的 16 位无符号整数。
- **`setInt32`**:写入 4 个字节的 32 位整数。
- **`setUint32`**:写入 4 个字节的 32 位无符号整数。
+- **`setBigInt64`**:写入 8 个字节的 64 位整数。
+- **`setBigUint64`**:写入 8 个字节的 64 位无符号整数。
+- **`setFloat16`**:写入 2 个字节的 16 位浮点数。
- **`setFloat32`**:写入 4 个字节的 32 位浮点数。
- **`setFloat64`**:写入 8 个字节的 64 位浮点数。
diff --git a/docs/async.md b/docs/async.md
index 532c1a3..86ce199 100644
--- a/docs/async.md
+++ b/docs/async.md
@@ -724,7 +724,7 @@ async function logInOrder(urls) {
## 顶层 await
-根据语法规格,`await`命令只能出现在 async 函数内部,否则都会报错。
+早期的语法规定是,`await`命令只能出现在 async 函数内部,否则都会报错。
```javascript
// 报错
@@ -733,7 +733,7 @@ const data = await fetch('https://api.example.com');
上面代码中,`await`命令独立使用,没有放在 async 函数里面,就会报错。
-目前,有一个[语法提案](https://github.com/tc39/proposal-top-level-await),允许在模块的顶层独立使用`await`命令,使得上面那行代码不会报错了。这个提案的目的,是借用`await`解决模块异步加载的问题。
+从 [ES2022](https://github.com/tc39/proposal-top-level-await) 开始,允许在模块的顶层独立使用`await`命令,使得上面那行代码不会报错了。它的主要目的是使用`await`解决模块异步加载的问题。
```javascript
// awaiting.js
@@ -749,19 +749,6 @@ export { output };
上面代码中,模块`awaiting.js`的输出值`output`,取决于异步操作。我们把异步操作包装在一个 async 函数里面,然后调用这个函数,只有等里面的异步操作都执行,变量`output`才会有值,否则就返回`undefined`。
-上面的代码也可以写成立即执行函数的形式。
-
-```javascript
-// awaiting.js
-let output;
-(async function main() {
- const dynamic = await import(someMission);
- const data = await fetch(url);
- output = someProcess(dynamic.default, data);
-})();
-export { output };
-```
-
下面是加载这个模块的写法。
```javascript
@@ -771,7 +758,7 @@ import { output } from "./awaiting.js";
function outputPlusValue(value) { return output + value }
console.log(outputPlusValue(100));
-setTimeout(() => console.log(outputPlusValue(100), 1000);
+setTimeout(() => console.log(outputPlusValue(100)), 1000);
```
上面代码中,`outputPlusValue()`的执行结果,完全取决于执行的时间。如果`awaiting.js`里面的异步操作没执行完,加载进来的`output`的值就是`undefined`。
@@ -801,7 +788,7 @@ function outputPlusValue(value) { return output + value }
promise.then(() => {
console.log(outputPlusValue(100));
- setTimeout(() => console.log(outputPlusValue(100), 1000);
+ setTimeout(() => console.log(outputPlusValue(100)), 1000);
});
```
@@ -828,7 +815,7 @@ import { output } from "./awaiting.js";
function outputPlusValue(value) { return output + value }
console.log(outputPlusValue(100));
-setTimeout(() => console.log(outputPlusValue(100), 1000);
+setTimeout(() => console.log(outputPlusValue(100)), 1000);
```
上面代码的写法,与普通的模块加载完全一样。也就是说,模块的使用者完全不用关心,依赖模块的内部有没有异步操作,正常加载即可。
diff --git a/docs/class-extends.md b/docs/class-extends.md
index e4a540f..0946d8b 100644
--- a/docs/class-extends.md
+++ b/docs/class-extends.md
@@ -2,7 +2,7 @@
## 简介
-Class 可以通过`extends`关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
+Class 可以通过`extends`关键字实现继承,让子类继承父类的属性和方法。extends 的写法比 ES5 的原型链继承,要清晰和方便很多。
```javascript
class Point {
@@ -12,9 +12,13 @@ class ColorPoint extends Point {
}
```
-上面代码定义了一个`ColorPoint`类,该类通过`extends`关键字,继承了`Point`类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个`Point`类。下面,我们在`ColorPoint`内部加上代码。
+上面示例中,`Point`是父类,`ColorPoint`是子类,它通过`extends`关键字,继承了`Point`类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个`Point`类。
+
+下面,我们在`ColorPoint`内部加上代码。
```javascript
+class Point { /* ... */ }
+
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
@@ -27,9 +31,9 @@ class ColorPoint extends Point {
}
```
-上面代码中,`constructor`方法和`toString`方法之中,都出现了`super`关键字,它在这里表示父类的构造函数,用来新建父类的`this`对象。
+上面示例中,`constructor()`方法和`toString()`方法内部,都出现了`super`关键字。`super`在这里表示父类的构造函数,用来新建一个父类的实例对象。
-子类必须在`constructor`方法中调用`super`方法,否则新建实例时会报错。这是因为子类自己的`this`对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用`super`方法,子类就得不到`this`对象。
+ES6 规定,子类必须在`constructor()`方法中调用`super()`,否则就会报错。这是因为子类自己的`this`对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用`super()`方法,子类就得不到自己的`this`对象。
```javascript
class Point { /* ... */ }
@@ -42,25 +46,34 @@ class ColorPoint extends Point {
let cp = new ColorPoint(); // ReferenceError
```
-上面代码中,`ColorPoint`继承了父类`Point`,但是它的构造函数没有调用`super`方法,导致新建实例时报错。
+上面代码中,`ColorPoint`继承了父类`Point`,但是它的构造函数没有调用`super()`,导致新建实例时报错。
-ES5 的继承,实质是先创造子类的实例对象`this`,然后再将父类的方法添加到`this`上面(`Parent.apply(this)`)。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到`this`上面(所以必须先调用`super`方法),然后再用子类的构造函数修改`this`。
+为什么子类的构造函数,一定要调用`super()`?原因就在于 ES6 的继承机制,与 ES5 完全不同。ES5 的继承机制,是先创造一个独立的子类的实例对象,然后再将父类的方法添加到这个对象上面,即“实例在前,继承在后”。ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后”。这就是为什么 ES6 的继承必须先调用`super()`方法,因为这一步会生成一个继承父类的`this`对象,没有这一步就无法继承父类。
-如果子类没有定义`constructor`方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有`constructor`方法。
+注意,这意味着新建子类实例时,父类的构造函数必定会先运行一次。
```javascript
-class ColorPoint extends Point {
+class Foo {
+ constructor() {
+ console.log(1);
+ }
}
-// 等同于
-class ColorPoint extends Point {
- constructor(...args) {
- super(...args);
+class Bar extends Foo {
+ constructor() {
+ super();
+ console.log(2);
}
}
+
+const bar = new Bar();
+// 1
+// 2
```
-另一个需要注意的地方是,在子类的构造函数中,只有调用`super`之后,才可以使用`this`关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有`super`方法才能调用父类实例。
+上面示例中,子类 Bar 新建实例时,会输出1和2。原因就是子类构造函数调用`super()`时,会执行一次父类构造函数。
+
+另一个需要注意的地方是,在子类的构造函数中,只有调用`super()`之后,才可以使用`this`关键字,否则会报错。这是因为子类实例的构建,必须先完成父类的继承,只有`super()`方法才能让子类实例继承父类。
```javascript
class Point {
@@ -79,9 +92,23 @@ class ColorPoint extends Point {
}
```
-上面代码中,子类的`constructor`方法没有调用`super`之前,就使用`this`关键字,结果报错,而放在`super`方法之后就是正确的。
+上面代码中,子类的`constructor()`方法没有调用`super()`之前,就使用`this`关键字,结果报错,而放在`super()`之后就是正确的。
-下面是生成子类实例的代码。
+如果子类没有定义`constructor()`方法,这个方法会默认添加,并且里面会调用`super()`。也就是说,不管有没有显式定义,任何一个子类都有`constructor()`方法。
+
+```javascript
+class ColorPoint extends Point {
+}
+
+// 等同于
+class ColorPoint extends Point {
+ constructor(...args) {
+ super(...args);
+ }
+}
+```
+
+有了子类的定义,就可以生成子类的实例了。
```javascript
let cp = new ColorPoint(25, 8, 'green');
@@ -90,9 +117,56 @@ cp instanceof ColorPoint // true
cp instanceof Point // true
```
-上面代码中,实例对象`cp`同时是`ColorPoint`和`Point`两个类的实例,这与 ES5 的行为完全一致。
+上面示例中,实例对象`cp`同时是`ColorPoint`和`Point`两个类的实例,这与 ES5 的行为完全一致。
+
+## 私有属性和私有方法的继承
+
+父类所有的属性和方法,都会被子类继承,除了私有的属性和方法。
+
+子类无法继承父类的私有属性,或者说,私有属性只能在定义它的 class 里面使用。
+
+```javascript
+class Foo {
+ #p = 1;
+ #m() {
+ console.log('hello');
+ }
+}
+
+class Bar extends Foo {
+ constructor() {
+ super();
+ console.log(this.#p); // 报错
+ this.#m(); // 报错
+ }
+}
+```
+
+上面示例中,子类 Bar 调用父类 Foo 的私有属性或私有方法,都会报错。
-最后,父类的静态方法,也会被子类继承。
+如果父类定义了私有属性的读写方法,子类就可以通过这些方法,读写私有属性。
+
+```javascript
+class Foo {
+ #p = 1;
+ getP() {
+ return this.#p;
+ }
+}
+
+class Bar extends Foo {
+ constructor() {
+ super();
+ console.log(this.getP()); // 1
+ }
+}
+```
+
+上面示例中,`getP()`是父类用来读取私有属性的方法,通过该方法,子类就可以读到父类的私有属性。
+
+## 静态属性和静态方法的继承
+
+父类的静态属性和静态方法,也会被子类继承。
```javascript
class A {
@@ -109,11 +183,54 @@ B.hello() // hello world
上面代码中,`hello()`是`A`类的静态方法,`B`继承`A`,也继承了`A`的静态方法。
+注意,静态属性是通过浅拷贝实现继承的。
+
+```javascript
+class A { static foo = 100; }
+class B extends A {
+ constructor() {
+ super();
+ B.foo--;
+ }
+}
+
+const b = new B();
+B.foo // 99
+A.foo // 100
+```
+
+上面示例中,`foo`是 A 类的静态属性,B 类继承了 A 类,因此也继承了这个属性。但是,在 B 类内部操作`B.foo`这个静态属性,影响不到`A.foo`,原因就是 B 类继承静态属性时,会采用浅拷贝,拷贝父类静态属性的值,因此`A.foo`和`B.foo`是两个彼此独立的属性。
+
+但是,由于这种拷贝是浅拷贝,如果父类的静态属性的值是一个对象,那么子类的静态属性也会指向这个对象,因为浅拷贝只会拷贝对象的内存地址。
+
+```javascript
+class A {
+ static foo = { n: 100 };
+}
+
+class B extends A {
+ constructor() {
+ super();
+ B.foo.n--;
+ }
+}
+
+const b = new B();
+B.foo.n // 99
+A.foo.n // 99
+```
+
+上面示例中,`A.foo`的值是一个对象,浅拷贝导致`B.foo`和`A.foo`指向同一个对象。所以,子类`B`修改这个对象的属性值,会影响到父类`A`。
+
## Object.getPrototypeOf()
-`Object.getPrototypeOf`方法可以用来从子类上获取父类。
+`Object.getPrototypeOf()`方法可以用来从子类上获取父类。
```javascript
+class Point { /*...*/ }
+
+class ColorPoint extends Point { /*...*/ }
+
Object.getPrototypeOf(ColorPoint) === Point
// true
```
@@ -124,7 +241,7 @@ Object.getPrototypeOf(ColorPoint) === Point
`super`这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
-第一种情况,`super`作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次`super`函数。
+第一种情况,`super`作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次`super()`函数。
```javascript
class A {}
@@ -136,9 +253,11 @@ class B extends A {
}
```
-上面代码中,子类`B`的构造函数之中的`super()`,代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。
+上面代码中,子类`B`的构造函数之中的`super()`,代表调用父类的构造函数。这是必须的,否则报错。
+
+调用`super()`的作用是形成子类的`this`对象,把父类的实例属性和方法放到这个`this`对象上面。子类在调用`super()`之前,是没有`this`对象的,任何对`this`的操作都要放在`super()`的后面。
-注意,`super`虽然代表了父类`A`的构造函数,但是返回的是子类`B`的实例,即`super`内部的`this`指的是`B`的实例,因此`super()`在这里相当于`A.prototype.constructor.call(this)`。
+注意,这里的`super`虽然代表了父类的构造函数,但是因为返回的是子类的`this`(即子类的实例对象),所以`super`内部的`this`代表子类的实例,而不是父类的实例,这里的`super()`相当于`A.prototype.constructor.call(this)`(在子类的`this`上运行父类的构造函数)。
```javascript
class A {
@@ -155,7 +274,26 @@ new A() // A
new B() // B
```
-上面代码中,`new.target`指向当前正在执行的函数。可以看到,在`super()`执行时,它指向的是子类`B`的构造函数,而不是父类`A`的构造函数。也就是说,`super()`内部的`this`指向的是`B`。
+上面示例中,`new.target`指向当前正在执行的函数。可以看到,在`super()`执行时(`new B()`),它指向的是子类`B`的构造函数,而不是父类`A`的构造函数。也就是说,`super()`内部的`this`指向的是`B`。
+
+不过,由于`super()`在子类构造方法中执行时,子类的属性和方法还没有绑定到`this`,所以如果存在同名属性,此时拿到的是父类的属性。
+
+```javascript
+class A {
+ name = 'A';
+ constructor() {
+ console.log('My name is ' + this.name);
+ }
+}
+
+class B extends A {
+ name = 'B';
+}
+
+const b = new B(); // My name is A
+```
+
+上面示例中,最后一行输出的是`A`,而不是`B`,原因就在于`super()`执行时,`B`的`name`属性还没有绑定到`this`,所以`this.name`拿到的是`A`类的`name`属性。
作为函数时,`super()`只能用在子类的构造函数之中,用在其他地方就会报错。
diff --git a/docs/class.md b/docs/class.md
index 3911940..15c2022 100644
--- a/docs/class.md
+++ b/docs/class.md
@@ -1,8 +1,6 @@
# Class 的基本语法
-## 简介
-
-### 类的由来
+## 类的由来
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。
@@ -122,7 +120,7 @@ Object.assign(Point.prototype, {
});
```
-`prototype`对象的`constructor()`属性,直接指向“类”的本身,这与 ES5 的行为是一致的。
+`prototype`对象的`constructor`属性,直接指向“类”的本身,这与 ES5 的行为是一致的。
```javascript
Point.prototype.constructor === Point // true
@@ -166,7 +164,7 @@ Object.getOwnPropertyNames(Point.prototype)
上面代码采用 ES5 的写法,`toString()`方法就是可枚举的。
-### constructor 方法
+## constructor() 方法
`constructor()`方法是类的默认方法,通过`new`命令生成对象实例时,自动调用该方法。一个类必须有`constructor()`方法,如果没有显式定义,一个空的`constructor()`方法会被默认添加。
@@ -210,9 +208,9 @@ Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
```
-### 类的实例
+## 类的实例
-生成类的实例的写法,与 ES5 完全一样,也是使用`new`命令。前面说过,如果忘记加上`new`,像函数那样调用`Class`,将会报错。
+生成类的实例的写法,与 ES5 完全一样,也是使用`new`命令。前面说过,如果忘记加上`new`,像函数那样调用`Class()`,将会报错。
```javascript
class Point {
@@ -226,12 +224,10 @@ var point = Point(2, 3);
var point = new Point(2, 3);
```
-与 ES5 一样,实例的属性除非显式定义在其本身(即定义在`this`对象上),否则都是定义在原型上(即定义在`class`上)。
+类的属性和方法,除非显式定义在其本身(即定义在`this`对象上),否则都是定义在原型上(即定义在`class`上)。
```javascript
-//定义类
class Point {
-
constructor(x, y) {
this.x = x;
this.y = y;
@@ -240,7 +236,6 @@ class Point {
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
-
}
var point = new Point(2, 3);
@@ -269,7 +264,7 @@ p1.__proto__ === p2.__proto__
这也意味着,可以通过实例的`__proto__`属性为“类”添加方法。
-> `__proto__` 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 `Object.getPrototypeOf` 方法来获取实例对象的原型,然后再来为原型添加方法/属性。
+> `__proto__` 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 `Object.getPrototypeOf()` 方法来获取实例对象的原型,然后再来为原型添加方法/属性。
```javascript
var p1 = new Point(2,3);
@@ -286,7 +281,63 @@ p3.printName() // "Oops"
上面代码在`p1`的原型上添加了一个`printName()`方法,由于`p1`的原型就是`p2`的原型,因此`p2`也可以调用这个方法。而且,此后新建的实例`p3`也可以调用这个方法。这意味着,使用实例的`__proto__`属性改写原型,必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。
-### 取值函数(getter)和存值函数(setter)
+## 实例属性的新写法
+
+[ES2022](https://github.com/tc39/proposal-class-fields) 为类的实例属性,又规定了一种新写法。实例属性现在除了可以定义在`constructor()`方法里面的`this`上面,也可以定义在类内部的最顶层。
+
+```javascript
+// 原来的写法
+class IncreasingCounter {
+ constructor() {
+ this._count = 0;
+ }
+ get value() {
+ console.log('Getting the current value!');
+ return this._count;
+ }
+ increment() {
+ this._count++;
+ }
+}
+```
+
+上面示例中,实例属性`_count`定义在`constructor()`方法里面的`this`上面。
+
+现在的新写法是,这个属性也可以定义在类的最顶层,其他都不变。
+
+```javascript
+class IncreasingCounter {
+ _count = 0;
+ get value() {
+ console.log('Getting the current value!');
+ return this._count;
+ }
+ increment() {
+ this._count++;
+ }
+}
+```
+
+上面代码中,实例属性`_count`与取值函数`value()`和`increment()`方法,处于同一个层级。这时,不需要在实例属性前面加上`this`。
+
+注意,新写法定义的属性是实例对象自身的属性,而不是定义在实例对象的原型上面。
+
+这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。
+
+```javascript
+class foo {
+ bar = 'hello';
+ baz = 'world';
+
+ constructor() {
+ // ...
+ }
+}
+```
+
+上面的代码,一眼就能看出,`foo`类有两个实例属性,一目了然。另外,写起来也比较简洁。
+
+## 取值函数(getter)和存值函数(setter)
与 ES5 一样,在“类”的内部可以使用`get`和`set`关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
@@ -341,7 +392,7 @@ var descriptor = Object.getOwnPropertyDescriptor(
上面代码中,存值函数和取值函数是定义在`html`属性的描述对象上面,这与 ES5 完全一致。
-### 属性表达式
+## 属性表达式
类的属性名,可以采用表达式。
@@ -361,7 +412,7 @@ class Square {
上面代码中,`Square`类的方法名`getArea`,是从表达式得到的。
-### Class 表达式
+## Class 表达式
与函数一样,类也可以使用表达式的形式定义。
@@ -407,142 +458,6 @@ person.sayName(); // "张三"
上面代码中,`person`是一个立即执行的类的实例。
-### 注意点
-
-**(1)严格模式**
-
-类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
-
-**(2)不存在提升**
-
-类不存在变量提升(hoist),这一点与 ES5 完全不同。
-
-```javascript
-new Foo(); // ReferenceError
-class Foo {}
-```
-
-上面代码中,`Foo`类使用在前,定义在后,这样会报错,因为 ES6 不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。
-
-```javascript
-{
- let Foo = class {};
- class Bar extends Foo {
- }
-}
-```
-
-上面的代码不会报错,因为`Bar`继承`Foo`的时候,`Foo`已经有定义了。但是,如果存在`class`的提升,上面代码就会报错,因为`class`会被提升到代码头部,而`let`命令是不提升的,所以导致`Bar`继承`Foo`的时候,`Foo`还没有定义。
-
-**(3)name 属性**
-
-由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被`Class`继承,包括`name`属性。
-
-```javascript
-class Point {}
-Point.name // "Point"
-```
-
-`name`属性总是返回紧跟在`class`关键字后面的类名。
-
-**(4)Generator 方法**
-
-如果某个方法之前加上星号(`*`),就表示该方法是一个 Generator 函数。
-
-```javascript
-class Foo {
- constructor(...args) {
- this.args = args;
- }
- * [Symbol.iterator]() {
- for (let arg of this.args) {
- yield arg;
- }
- }
-}
-
-for (let x of new Foo('hello', 'world')) {
- console.log(x);
-}
-// hello
-// world
-```
-
-上面代码中,`Foo`类的`Symbol.iterator`方法前有一个星号,表示该方法是一个 Generator 函数。`Symbol.iterator`方法返回一个`Foo`类的默认遍历器,`for...of`循环会自动调用这个遍历器。
-
-**(5)this 的指向**
-
-类的方法内部如果含有`this`,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
-
-```javascript
-class Logger {
- printName(name = 'there') {
- this.print(`Hello ${name}`);
- }
-
- print(text) {
- console.log(text);
- }
-}
-
-const logger = new Logger();
-const { printName } = logger;
-printName(); // TypeError: Cannot read property 'print' of undefined
-```
-
-上面代码中,`printName`方法中的`this`,默认指向`Logger`类的实例。但是,如果将这个方法提取出来单独使用,`this`会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是`undefined`),从而导致找不到`print`方法而报错。
-
-一个比较简单的解决方法是,在构造方法中绑定`this`,这样就不会找不到`print`方法了。
-
-```javascript
-class Logger {
- constructor() {
- this.printName = this.printName.bind(this);
- }
-
- // ...
-}
-```
-
-另一种解决方法是使用箭头函数。
-
-```javascript
-class Obj {
- constructor() {
- this.getThis = () => this;
- }
-}
-
-const myObj = new Obj();
-myObj.getThis() === myObj // true
-```
-
-箭头函数内部的`this`总是指向定义时所在的对象。上面代码中,箭头函数位于构造函数内部,它的定义生效的时候,是在构造函数执行的时候。这时,箭头函数所在的运行环境,肯定是实例对象,所以`this`会总是指向实例对象。
-
-还有一种解决方法是使用`Proxy`,获取方法的时候,自动绑定`this`。
-
-```javascript
-function selfish (target) {
- const cache = new WeakMap();
- const handler = {
- get (target, key) {
- const value = Reflect.get(target, key);
- if (typeof value !== 'function') {
- return value;
- }
- if (!cache.has(value)) {
- cache.set(value, value.bind(target));
- }
- return cache.get(value);
- }
- };
- const proxy = new Proxy(target, handler);
- return proxy;
-}
-
-const logger = selfish(new Logger());
-```
-
## 静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上`static`关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
@@ -618,57 +533,6 @@ class Bar extends Foo {
Bar.classMethod() // "hello, too"
```
-## 实例属性的新写法
-
-实例属性除了定义在`constructor()`方法里面的`this`上面,也可以定义在类的最顶层。
-
-```javascript
-class IncreasingCounter {
- constructor() {
- this._count = 0;
- }
- get value() {
- console.log('Getting the current value!');
- return this._count;
- }
- increment() {
- this._count++;
- }
-}
-```
-
-上面代码中,实例属性`this._count`定义在`constructor()`方法里面。另一种写法是,这个属性也可以定义在类的最顶层,其他都不变。
-
-```javascript
-class IncreasingCounter {
- _count = 0;
- get value() {
- console.log('Getting the current value!');
- return this._count;
- }
- increment() {
- this._count++;
- }
-}
-```
-
-上面代码中,实例属性`_count`与取值函数`value()`和`increment()`方法,处于同一个层级。这时,不需要在实例属性前面加上`this`。
-
-这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。
-
-```javascript
-class foo {
- bar = 'hello';
- baz = 'world';
-
- constructor() {
- // ...
- }
-}
-```
-
-上面的代码,一眼就能看出,`foo`类有两个实例属性,一目了然。另外,写起来也比较简洁。
-
## 静态属性
静态属性指的是 Class 本身的属性,即`Class.propName`,而不是定义在实例对象(`this`)上的属性。
@@ -714,9 +578,9 @@ class Foo {
## 私有方法和私有属性
-### 现有的解决方案
+### 早期解决方案
-私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。
+私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但早期的 ES6 不提供,只能通过变通方法模拟实现。
一种做法是在命名上加以区别。
@@ -790,9 +654,9 @@ Reflect.ownKeys(myClass.prototype)
上面代码中,Symbol 值的属性名依然可以从类的外部拿到。
-### 私有属性的提案
+### 私有属性的正式写法
-目前,有一个[提案](https://github.com/tc39/proposal-private-methods),为`class`加了私有属性。方法是在属性名之前,使用`#`表示。
+[ES2022](https://github.com/tc39/proposal-class-fields)正式为`class`添加了私有属性,方法是在属性名之前使用`#`表示。
```javascript
class IncreasingCounter {
@@ -815,9 +679,31 @@ counter.#count // 报错
counter.#count = 42 // 报错
```
-上面代码在类的外部,读取私有属性,就会报错。
+上面示例中,在类的外部,读取或写入私有属性`#count`,都会报错。
+
+注意,[从 Chrome 111 开始](https://developer.chrome.com/blog/new-in-devtools-111/#misc),开发者工具里面可以读写私有属性,不会报错,原因是 Chrome 团队认为这样方便调试。
-下面是另一个例子。
+另外,不管在类的内部或外部,读取一个不存在的私有属性,也都会报错。这跟公开属性的行为完全不同,如果读取一个不存在的公开属性,不会报错,只会返回`undefined`。
+
+```javascript
+class IncreasingCounter {
+ #count = 0;
+ get value() {
+ console.log('Getting the current value!');
+ return this.#myCount; // 报错
+ }
+ increment() {
+ this.#count++;
+ }
+}
+
+const counter = new IncreasingCounter();
+counter.#myCount // 报错
+```
+
+上面示例中,`#myCount`是一个不存在的私有属性,不管在函数内部或外部,读取该属性都会导致报错。
+
+注意,私有属性的属性名必须包括`#`,如果不带`#`,会被当作另一个属性。
```javascript
class Point {
@@ -839,8 +725,6 @@ class Point {
上面代码中,`#x`就是私有属性,在`Point`类之外是读取不到这个属性的。由于井号`#`是属性名的一部分,使用时必须带有`#`一起使用,所以`#x`和`x`是两个不同的属性。
-之所以要引入一个新的前缀`#`表示私有属性,而没有采用`private`关键字,是因为 JavaScript 是一门动态语言,没有类型声明,使用独立的符号似乎是唯一的比较方便可靠的方法,能够准确地区分一种属性是否为私有属性。另外,Ruby 语言使用`@`表示私有属性,ES6 没有用这个符号而使用`#`,是因为`@`已经被留给了 Decorator。
-
这种写法不仅可以写私有属性,还可以用来写私有方法。
```javascript
@@ -860,7 +744,7 @@ class Foo {
}
```
-上面代码中,`#sum()`就是一个私有方法。
+上面示例中,`#sum()`就是一个私有方法。
另外,私有属性也可以设置 getter 和 setter 方法。
@@ -869,18 +753,17 @@ class Counter {
#xValue = 0;
constructor() {
- super();
- // ...
+ console.log(this.#x);
}
- get #x() { return #xValue; }
+ get #x() { return this.#xValue; }
set #x(value) {
this.#xValue = value;
}
}
```
-上面代码中,`#x`是一个私有属性,它的读写都通过`get #x()`和`set #x()`来完成。
+上面代码中,`#x`是一个私有属性,它的读写都通过`get #x()`和`set #x()`操作另一个私有属性`#xValue`来完成。
私有属性不限于从`this`引用,只要是在类的内部,实例也可以引用私有属性。
@@ -926,114 +809,284 @@ FakeMath.#computeRandomNumber() // 报错
### in 运算符
-`try...catch`结构可以用来判断是否存在某个私有属性。
+前面说过,直接访问某个类不存在的私有属性会报错,但是访问不存在的公开属性不会报错。这个特性可以用来判断,某个对象是否为类的实例。
```javascript
-class A {
- use(obj) {
+class C {
+ #brand;
+
+ static isC(obj) {
try {
- obj.#foo;
+ obj.#brand;
+ return true;
} catch {
- // 私有属性 #foo 不存在
+ return false;
}
}
}
-
-const a = new A();
-a.use(a); // 报错
```
-上面示例中,类`A`并不存在私有属性`#foo`,所以`try...catch`报错了。
+上面示例中,类`C`的静态方法`isC()`就用来判断,某个对象是否为`C`的实例。它采用的方法就是,访问该对象的私有属性`#brand`。如果不报错,就会返回`true`;如果报错,就说明该对象不是当前类的实例,从而`catch`部分返回`false`。
-这样的写法很麻烦,可读性很差,V8 引擎改进了`in`运算符,使它也可以用来判断私有属性。
+因此,`try...catch`结构可以用来判断某个私有属性是否存在。但是,这样的写法很麻烦,代码可读性很差,[ES2022](https://github.com/tc39/proposal-private-fields-in-in) 改进了`in`运算符,使它也可以用来判断私有属性。
```javascript
-class A {
- use(obj) {
- if (#foo in obj) {
- // 私有属性 #foo 存在
+class C {
+ #brand;
+
+ static isC(obj) {
+ if (#brand in obj) {
+ // 私有属性 #brand 存在
+ return true;
} else {
- // 私有属性 #foo 不存在
+ // 私有属性 #brand 不存在
+ return false;
}
}
}
```
-上面示例中,`in`运算符判断当前类`A`的实例,是否有私有属性`#foo`,如果有返回`true`,否则返回`false`。
+上面示例中,`in`运算符判断某个对象是否有私有属性`#brand`。它不会报错,而是返回一个布尔值。
-`in`也可以跟`this`一起配合使用。
+这种用法的`in`,也可以跟`this`一起配合使用。
```javascript
class A {
#foo = 0;
m() {
console.log(#foo in this); // true
- console.log(#bar in this); // false
}
}
```
-注意,判断私有属性时,`in`只能用在定义该私有属性的类的内部。
+注意,判断私有属性时,`in`只能用在类的内部。另外,判断所针对的私有属性,一定要先声明,否则会报错。
```javascript
class A {
- #foo = 0;
- static test(obj) {
- console.log(#foo in obj);
+ m() {
+ console.log(#foo in this); // 报错
}
}
+```
-A.test(new A()) // true
-A.test({}) // false
+上面示例中,私有属性`#foo`没有声明,就直接用于`in`运算符的判断,导致报错。
-class B {
- #foo = 0;
+## 静态块
+
+静态属性的一个问题是,如果它有初始化逻辑,这个逻辑要么写在类的外部,要么写在`constructor()`方法里面。
+
+```javascript
+class C {
+ static x = 234;
+ static y;
+ static z;
}
-A.test(new B()) // false
+try {
+ const obj = doSomethingWith(C.x);
+ C.y = obj.y
+ C.z = obj.z;
+} catch {
+ C.y = ...;
+ C.z = ...;
+}
```
-上面示例中,类`A`的私有属性`#foo`,只能在类`A`内部使用`in`运算符判断,而且只对`A`的实例返回`true`,对于其他对象都返回`false`。
+上面示例中,静态属性`y`和`z`的值依赖于静态属性`x`的运算结果,这段初始化逻辑写在类的外部(上例的`try...catch`代码块)。另一种方法是写到类的`constructor()`方法里面。这两种方法都不是很理想,前者是将类的内部逻辑写到了外部,后者则是每次新建实例都会运行一次。
-子类从父类继承的私有属性,也可以使用`in`运算符来判断。
+为了解决这个问题,ES2022 引入了[静态块](https://github.com/tc39/proposal-class-static-block)(static block),允许在类的内部设置一个代码块,在类生成时运行且只运行一次,主要作用是对静态属性进行初始化。以后,新建类的实例时,这个块就不运行了。
```javascript
-class A {
- #foo = 0;
- static test(obj) {
- console.log(#foo in obj);
+class C {
+ static x = ...;
+ static y;
+ static z;
+
+ static {
+ try {
+ const obj = doSomethingWith(this.x);
+ this.y = obj.y;
+ this.z = obj.z;
+ }
+ catch {
+ this.y = ...;
+ this.z = ...;
+ }
}
}
+```
+
+上面代码中,类的内部有一个 static 代码块,这就是静态块。它的好处是将静态属性`y`和`z`的初始化逻辑,写入了类的内部,而且只运行一次。
-class SubA extend A {};
+每个类允许有多个静态块,每个静态块中只能访问之前声明的静态属性。另外,静态块的内部不能有`return`语句。
-A.test(new SubA()) // true
+静态块内部可以使用类名或`this`,指代当前类。
+
+```javascript
+class C {
+ static x = 1;
+ static {
+ this.x; // 1
+ // 或者
+ C.x; // 1
+ }
+}
```
-上面示例中,`SubA`从父类继承了私有属性`#foo`,`in`运算符也有效。
+上面示例中,`this.x`和`C.x`都能获取静态属性`x`。
-注意,`in`运算符对于`Object.create()`、`Object.setPrototypeOf`形成的继承,是无效的,因为这种继承不会传递私有属性。
+除了静态属性的初始化,静态块还有一个作用,就是将私有属性与类的外部代码分享。
```javascript
-class A {
- #foo = 0;
- static test(obj) {
- console.log(#foo in obj);
+let getX;
+
+export class C {
+ #x = 1;
+ static {
+ getX = obj => obj.#x;
+ }
+}
+
+console.log(getX(new C())); // 1
+```
+
+上面示例中,`#x`是类的私有属性,如果类外部的`getX()`方法希望获取这个属性,以前是要写在类的`constructor()`方法里面,这样的话,每次新建实例都会定义一次`getX()`方法。现在可以写在静态块里面,这样的话,只在类生成时定义一次。
+
+## 类的注意点
+
+### 严格模式
+
+类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
+
+### 不存在提升
+
+类不存在变量提升(hoist),这一点与 ES5 完全不同。
+
+```javascript
+new Foo(); // ReferenceError
+class Foo {}
+```
+
+上面代码中,`Foo`类使用在前,定义在后,这样会报错,因为 ES6 不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。
+
+```javascript
+{
+ let Foo = class {};
+ class Bar extends Foo {
+ }
+}
+```
+
+上面的代码不会报错,因为`Bar`继承`Foo`的时候,`Foo`已经有定义了。但是,如果存在`class`的提升,上面代码就会报错,因为`class`会被提升到代码头部,而定义`Foo`的那一行没有提升,导致`Bar`继承`Foo`的时候,`Foo`还没有定义。
+
+### name 属性
+
+由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被`Class`继承,包括`name`属性。
+
+```javascript
+class Point {}
+Point.name // "Point"
+```
+
+`name`属性总是返回紧跟在`class`关键字后面的类名。
+
+### Generator 方法
+
+如果某个方法之前加上星号(`*`),就表示该方法是一个 Generator 函数。
+
+```javascript
+class Foo {
+ constructor(...args) {
+ this.args = args;
+ }
+ * [Symbol.iterator]() {
+ for (let arg of this.args) {
+ yield arg;
+ }
+ }
+}
+
+for (let x of new Foo('hello', 'world')) {
+ console.log(x);
+}
+// hello
+// world
+```
+
+上面代码中,`Foo`类的`Symbol.iterator`方法前有一个星号,表示该方法是一个 Generator 函数。`Symbol.iterator`方法返回一个`Foo`类的默认遍历器,`for...of`循环会自动调用这个遍历器。
+
+### this 的指向
+
+类的方法内部如果含有`this`,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
+
+```javascript
+class Logger {
+ printName(name = 'there') {
+ this.print(`Hello ${name}`);
+ }
+
+ print(text) {
+ console.log(text);
+ }
+}
+
+const logger = new Logger();
+const { printName } = logger;
+printName(); // TypeError: Cannot read property 'print' of undefined
+```
+
+上面代码中,`printName`方法中的`this`,默认指向`Logger`类的实例。但是,如果将这个方法提取出来单独使用,`this`会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是`undefined`),从而导致找不到`print`方法而报错。
+
+一个比较简单的解决方法是,在构造方法中绑定`this`,这样就不会找不到`print`方法了。
+
+```javascript
+class Logger {
+ constructor() {
+ this.printName = this.printName.bind(this);
}
+
+ // ...
}
-const a = new A();
+```
-const o1 = Object.create(a);
-A.test(o1) // false
-A.test(o1.__proto__) // true
+另一种解决方法是使用箭头函数。
-const o2 = {};
-Object.setPrototypeOf(o2, A);
-A.test(o2) // false
-A.test(o2.__proto__) // true
+```javascript
+class Obj {
+ constructor() {
+ this.getThis = () => this;
+ }
+}
+
+const myObj = new Obj();
+myObj.getThis() === myObj // true
```
-上面示例中,对于修改原型链形成的继承,子类都取不到父类的私有属性,所以`in`运算符无效。
+箭头函数内部的`this`总是指向定义时所在的对象。上面代码中,箭头函数位于构造函数内部,它的定义生效的时候,是在构造函数执行的时候。这时,箭头函数所在的运行环境,肯定是实例对象,所以`this`会总是指向实例对象。
+
+还有一种解决方法是使用`Proxy`,获取方法的时候,自动绑定`this`。
+
+```javascript
+function selfish (target) {
+ const cache = new WeakMap();
+ const handler = {
+ get (target, key) {
+ const value = Reflect.get(target, key);
+ if (typeof value !== 'function') {
+ return value;
+ }
+ if (!cache.has(value)) {
+ cache.set(value, value.bind(target));
+ }
+ return cache.get(value);
+ }
+ };
+ const proxy = new Proxy(target, handler);
+ return proxy;
+}
+
+const logger = selfish(new Logger());
+```
## new.target 属性
@@ -1123,3 +1176,4 @@ var y = new Rectangle(3, 4); // 正确
上面代码中,`Shape`类不能被实例化,只能用于继承。
注意,在函数外部,使用`new.target`会报错。
+
diff --git a/docs/decorator.md b/docs/decorator.md
index cb8517a..0577d18 100644
--- a/docs/decorator.md
+++ b/docs/decorator.md
@@ -1,10 +1,19 @@
# 装饰器
-[说明] Decorator 提案经过了大幅修改,目前还没有定案,不知道语法会不会再变。下面的内容完全依据以前的提案,已经有点过时了。等待定案以后,需要完全重写。
+[说明] Decorator 提案经历了重大的语法变化,目前处于第三阶段,定案之前不知道是否还有变化。本章现在属于草稿阶段,凡是标注“新语法”的章节,都是基于当前的语法,不过没有详细整理,只是一些原始材料;未标注“新语法”的章节基于以前的语法,是过去遗留的稿子。之所以保留以前的内容,有两个原因,一是 TypeScript 装饰器会用到这些语法,二是里面包含不少有价值的内容。等到标准完全定案,本章将彻底重写:删去过时内容,补充材料,增加解释。(2022年6月)
-装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法。许多面向对象的语言都有这项功能,目前有一个[提案](https://github.com/tc39/proposal-decorators)将其引入了 ECMAScript。
+## 简介(新语法)
-装饰器是一种函数,写成`@ + 函数名`。它可以放在类和类方法的定义前面。
+装饰器(Decorator)用来增强 JavaScript 类(class)的功能,许多面向对象的语言都有这种语法,目前有一个[提案](https://github.com/tc39/proposal-decorators)将其引入了 ECMAScript。
+
+装饰器是一种函数,写成`@ + 函数名`,可以用来装饰四种类型的值。
+
+- 类
+- 类的属性
+- 类的方法
+- 属性存取器(accessor)
+
+下面的例子是装饰器放在类名和类方法名之前,大家可以感受一下写法。
```javascript
@frozen class Foo {
@@ -17,7 +26,47 @@
}
```
-上面代码一共使用了四个装饰器,一个用在类本身,另外三个用在类方法。它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能。
+上面代码一共使用了四个装饰器,一个用在类本身(@frozen),另外三个用在类方法(@configurable()、@enumerable()、@throttle())。它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能。
+
+## 装饰器 API(新语法)
+
+装饰器是一个函数,API 的类型描述如下(TypeScript 写法)。
+
+```typescript
+type Decorator = (value: Input, context: {
+ kind: string;
+ name: string | symbol;
+ access: {
+ get?(): unknown;
+ set?(value: unknown): void;
+ };
+ private?: boolean;
+ static?: boolean;
+ addInitializer?(initializer: () => void): void;
+}) => Output | void;
+```
+
+装饰器函数有两个参数。运行时,JavaScript 引擎会提供这两个参数。
+
+- `value`:所要装饰的值,某些情况下可能是`undefined`(装饰属性时)。
+- `context`:上下文信息对象。
+
+装饰器函数的返回值,是一个新版本的装饰对象,但也可以不返回任何值(void)。
+
+`context`对象有很多属性,其中`kind`属性表示属于哪一种装饰,其他属性的含义如下。
+
+- `kind`:字符串,表示装饰类型,可能的取值有`class`、`method`、`getter`、`setter`、`field`、`accessor`。
+- `name`:被装饰的值的名称: The name of the value, or in the case of private elements the description of it (e.g. the readable name).
+- `access`:对象,包含访问这个值的方法,即存值器和取值器。
+- `static`: 布尔值,该值是否为静态元素。
+- `private`:布尔值,该值是否为私有元素。
+- `addInitializer`:函数,允许用户增加初始化逻辑。
+
+装饰器的执行步骤如下。
+
+1. 计算各个装饰器的值,按照从左到右,从上到下的顺序。
+1. 调用方法装饰器。
+1. 调用类装饰器。
## 类的装饰
@@ -109,7 +158,7 @@ export function mixins(...list) {
}
// main.js
-import { mixins } from './mixins'
+import { mixins } from './mixins.js'
const Foo = {
foo() { console.log('foo') }
@@ -154,6 +203,156 @@ export default class MyReactComponent extends React.Component {}
相对来说,后一种写法看上去更容易理解。
+## 类装饰器(新语法)
+
+类装饰器的类型描述如下。
+
+```typescript
+type ClassDecorator = (value: Function, context: {
+ kind: "class";
+ name: string | undefined;
+ addInitializer(initializer: () => void): void;
+}) => Function | void;
+```
+
+类装饰器的第一个参数,就是被装饰的类。第二个参数是上下文对象,如果被装饰的类是一个匿名类,`name`属性就为`undefined`。
+
+类装饰器可以返回一个新的类,取代原来的类,也可以不返回任何值。如果返回的不是构造函数,就会报错。
+
+下面是一个例子。
+
+```javascript
+function logged(value, { kind, name }) {
+ if (kind === "class") {
+ return class extends value {
+ constructor(...args) {
+ super(...args);
+ console.log(`constructing an instance of ${name} with arguments ${args.join(", ")}`);
+ }
+ }
+ }
+
+ // ...
+}
+
+@logged
+class C {}
+
+new C(1);
+// constructing an instance of C with arguments 1
+```
+
+如果不使用装饰器,类装饰器实际上执行的是下面的语法。
+
+```javascript
+class C {}
+
+C = logged(C, {
+ kind: "class",
+ name: "C",
+}) ?? C;
+
+new C(1);
+```
+
+## 方法装饰器(新语法)
+
+方法装饰器会修改类的方法。
+
+```javascript
+class C {
+ @trace
+ toString() {
+ return 'C';
+ }
+}
+
+// 相当于
+C.prototype.toString = trace(C.prototype.toString);
+```
+
+上面示例中,`@trace`装饰`toString()`方法,就相当于修改了该方法。
+
+方法装饰器使用 TypeScript 描述类型如下。
+
+```typescript
+type ClassMethodDecorator = (value: Function, context: {
+ kind: "method";
+ name: string | symbol;
+ access: { get(): unknown };
+ static: boolean;
+ private: boolean;
+ addInitializer(initializer: () => void): void;
+}) => Function | void;
+```
+
+方法装饰器的第一个参数`value`,就是所要装饰的方法。
+
+方法装饰器可以返回一个新函数,取代原来的方法,也可以不返回值,表示依然使用原来的方法。如果返回其他类型的值,就会报错。下面是一个例子。
+
+```javascript
+function replaceMethod() {
+ return function () {
+ return `How are you, ${this.name}?`;
+ }
+}
+
+class Person {
+ constructor(name) {
+ this.name = name;
+ }
+ @replaceMethod
+ hello() {
+ return `Hi ${this.name}!`;
+ }
+}
+
+const robin = new Person('Robin');
+
+robin.hello(), 'How are you, Robin?'
+```
+
+上面示例中,`@replaceMethod`返回了一个新函数,取代了原来的`hello()`方法。
+
+```typescript
+function logged(value, { kind, name }) {
+ if (kind === "method") {
+ return function (...args) {
+ console.log(`starting ${name} with arguments ${args.join(", ")}`);
+ const ret = value.call(this, ...args);
+ console.log(`ending ${name}`);
+ return ret;
+ };
+ }
+}
+
+class C {
+ @logged
+ m(arg) {}
+}
+
+new C().m(1);
+// starting m with arguments 1
+// ending m
+```
+
+上面示例中,装饰器`@logged`返回一个函数,代替原来的`m()`方法。
+
+这里的装饰器实际上是一个语法糖,真正的操作是像下面这样,改掉原型链上面`m()`方法。
+
+```javascript
+class C {
+ m(arg) {}
+}
+
+C.prototype.m = logged(C.prototype.m, {
+ kind: "method",
+ name: "m",
+ static: false,
+ private: false,
+}) ?? C.prototype.m;
+```
+
## 方法的装饰
装饰器不仅可以装饰类,还可以装饰类的属性。
@@ -366,6 +565,355 @@ function loggingDecorator(wrapped) {
const wrapped = loggingDecorator(doSomething);
```
+## 存取器装饰器(新语法)
+
+存取器装饰器使用 TypeScript 描述的类型如下。
+
+```typescript
+type ClassGetterDecorator = (value: Function, context: {
+ kind: "getter";
+ name: string | symbol;
+ access: { get(): unknown };
+ static: boolean;
+ private: boolean;
+ addInitializer(initializer: () => void): void;
+}) => Function | void;
+
+type ClassSetterDecorator = (value: Function, context: {
+ kind: "setter";
+ name: string | symbol;
+ access: { set(value: unknown): void };
+ static: boolean;
+ private: boolean;
+ addInitializer(initializer: () => void): void;
+}) => Function | void;
+```
+
+存取器装饰器的第一个参数就是原始的存值器(setter)和取值器(getter)。
+
+存取器装饰器的返回值如果是一个函数,就会取代原来的存取器。本质上,就像方法装饰器一样,修改发生在类的原型对象上。它也可以不返回任何值,继续使用原来的存取器。如果返回其他类型的值,就会报错。
+
+存取器装饰器对存值器(setter)和取值器(getter)是分开作用的。下面的例子里面,`@foo`只装饰`get x()`,不装饰`set x()`。
+
+```javascript
+class C {
+ @foo
+ get x() {
+ // ...
+ }
+
+ set x(val) {
+ // ...
+ }
+}
+```
+
+上一节的`@logged`装饰器稍加修改,就可以用在存取装饰器。
+
+```javascript
+function logged(value, { kind, name }) {
+ if (kind === "method" || kind === "getter" || kind === "setter") {
+ return function (...args) {
+ console.log(`starting ${name} with arguments ${args.join(", ")}`);
+ const ret = value.call(this, ...args);
+ console.log(`ending ${name}`);
+ return ret;
+ };
+ }
+}
+
+class C {
+ @logged
+ set x(arg) {}
+}
+
+new C().x = 1
+// starting x with arguments 1
+// ending x
+```
+
+如果去掉语法糖,使用传统语法来写,就是改掉了类的原型链。
+
+```javascript
+class C {
+ set x(arg) {}
+}
+
+let { set } = Object.getOwnPropertyDescriptor(C.prototype, "x");
+set = logged(set, {
+ kind: "setter",
+ name: "x",
+ static: false,
+ private: false,
+}) ?? set;
+
+Object.defineProperty(C.prototype, "x", { set });
+```
+
+## 属性装饰器(新语法)
+
+属性装饰器的类型描述如下。
+
+```typescript
+type ClassFieldDecorator = (value: undefined, context: {
+ kind: "field";
+ name: string | symbol;
+ access: { get(): unknown, set(value: unknown): void };
+ static: boolean;
+ private: boolean;
+}) => (initialValue: unknown) => unknown | void;
+```
+
+属性装饰器的第一个参数是`undefined`,即不输入值。用户可以选择让装饰器返回一个初始化函数,当该属性被赋值时,这个初始化函数会自动运行,它会收到属性的初始值,然后返回一个新的初始值。属性装饰器也可以不返回任何值。除了这两种情况,返回其他类型的值都会报错。
+
+下面是一个例子。
+
+```javascript
+function logged(value, { kind, name }) {
+ if (kind === "field") {
+ return function (initialValue) {
+ console.log(`initializing ${name} with value ${initialValue}`);
+ return initialValue;
+ };
+ }
+
+ // ...
+}
+
+class C {
+ @logged x = 1;
+}
+
+new C();
+// initializing x with value 1
+```
+
+如果不使用装饰器语法,属性装饰器的实际作用如下。
+
+```javascript
+let initializeX = logged(undefined, {
+ kind: "field",
+ name: "x",
+ static: false,
+ private: false,
+}) ?? (initialValue) => initialValue;
+
+class C {
+ x = initializeX.call(this, 1);
+}
+```
+
+## accessor 命令(新语法)
+
+类装饰器引入了一个新命令`accessor`,用来属性的前缀。
+
+```javascript
+class C {
+ accessor x = 1;
+}
+```
+
+它是一种简写形式,相当于声明属性`x`是私有属性`#x`的存取接口。上面的代码等同于下面的代码。
+
+```javascript
+class C {
+ #x = 1;
+
+ get x() {
+ return this.#x;
+ }
+
+ set x(val) {
+ this.#x = val;
+ }
+}
+```
+
+`accessor`命令前面,还可以加上`static`命令和`private`命令。
+
+```javascript
+class C {
+ static accessor x = 1;
+ accessor #y = 2;
+}
+```
+
+`accessor`命令前面还可以接受属性装饰器。
+
+```javascript
+function logged(value, { kind, name }) {
+ if (kind === "accessor") {
+ let { get, set } = value;
+
+ return {
+ get() {
+ console.log(`getting ${name}`);
+
+ return get.call(this);
+ },
+
+ set(val) {
+ console.log(`setting ${name} to ${val}`);
+
+ return set.call(this, val);
+ },
+
+ init(initialValue) {
+ console.log(`initializing ${name} with value ${initialValue}`);
+ return initialValue;
+ }
+ };
+ }
+
+ // ...
+}
+
+class C {
+ @logged accessor x = 1;
+}
+
+let c = new C();
+// initializing x with value 1
+c.x;
+// getting x
+c.x = 123;
+// setting x to 123
+```
+
+上面的示例等同于使用`@logged`装饰器,改写`accessor`属性的 getter 和 setter 方法。
+
+用于`accessor`的属性装饰器的类型描述如下。
+
+```typescript
+type ClassAutoAccessorDecorator = (
+ value: {
+ get: () => unknown;
+ set(value: unknown) => void;
+ },
+ context: {
+ kind: "accessor";
+ name: string | symbol;
+ access: { get(): unknown, set(value: unknown): void };
+ static: boolean;
+ private: boolean;
+ addInitializer(initializer: () => void): void;
+ }
+) => {
+ get?: () => unknown;
+ set?: (value: unknown) => void;
+ initialize?: (initialValue: unknown) => unknown;
+} | void;
+```
+
+`accessor`命令的第一个参数接收到的是一个对象,包含了`accessor`命令定义的属性的存取器 get 和 set。属性装饰器可以返回一个新对象,其中包含了新的存取器,用来取代原来的,即相当于拦截了原来的存取器。此外,返回的对象还可以包括一个`initialize`函数,用来改变私有属性的初始值。装饰器也可以不返回值,如果返回的是其他类型的值,或者包含其他属性的对象,就会报错。
+
+## addInitializer() 方法(新语法)
+
+除了属性装饰器,其他装饰器的上下文对象还包括一个`addInitializer()`方法,用来完成初始化操作。
+
+它的运行时间如下。
+
+- 类装饰器:在类被完全定义之后。
+- 方法装饰器:在类构造期间运行,在属性初始化之前。
+- 静态方法装饰器:在类定义期间运行,早于静态属性定义,但晚于类方法的定义。
+
+下面是一个例子。
+
+```javascript
+function customElement(name) {
+ return (value, { addInitializer }) => {
+ addInitializer(function() {
+ customElements.define(name, this);
+ });
+ }
+}
+
+@customElement('my-element')
+class MyElement extends HTMLElement {
+ static get observedAttributes() {
+ return ['some', 'attrs'];
+ }
+}
+```
+
+上面的代码等同于下面不使用装饰器的代码。
+
+```javascript
+class MyElement {
+ static get observedAttributes() {
+ return ['some', 'attrs'];
+ }
+}
+
+let initializersForMyElement = [];
+
+MyElement = customElement('my-element')(MyElement, {
+ kind: "class",
+ name: "MyElement",
+ addInitializer(fn) {
+ initializersForMyElement.push(fn);
+ },
+}) ?? MyElement;
+
+for (let initializer of initializersForMyElement) {
+ initializer.call(MyElement);
+}
+```
+
+下面是方法装饰器的例子。
+
+```javascript
+function bound(value, { name, addInitializer }) {
+ addInitializer(function () {
+ this[name] = this[name].bind(this);
+ });
+}
+
+class C {
+ message = "hello!";
+
+ @bound
+ m() {
+ console.log(this.message);
+ }
+}
+
+let { m } = new C();
+
+m(); // hello!
+```
+
+上面的代码等同于下面不使用装饰器的代码。
+
+```javascript
+class C {
+ constructor() {
+ for (let initializer of initializersForM) {
+ initializer.call(this);
+ }
+
+ this.message = "hello!";
+ }
+
+ m() {}
+}
+
+let initializersForM = []
+
+C.prototype.m = bound(
+ C.prototype.m,
+ {
+ kind: "method",
+ name: "m",
+ static: false,
+ private: false,
+ addInitializer(fn) {
+ initializersForM.push(fn);
+ },
+ }
+) ?? C.prototype.m;
+```
+
## core-decorators.js
[core-decorators.js](https://github.com/jayphelps/core-decorators.js)是一个第三方模块,提供了几个常见的装饰器,通过它可以更好地理解装饰器。
@@ -591,7 +1139,7 @@ export function mixins(...list) {
然后,就可以使用上面这个装饰器,为类“混入”各种方法。
```javascript
-import { mixins } from './mixins';
+import { mixins } from './mixins.js';
const Foo = {
foo() { console.log('foo') }
diff --git a/docs/function.md b/docs/function.md
index d5a8e4e..99695e4 100644
--- a/docs/function.md
+++ b/docs/function.md
@@ -17,7 +17,7 @@ log('Hello', 'China') // Hello China
log('Hello', '') // Hello World
```
-上面代码检查函数`log`的参数`y`有没有赋值,如果没有,则指定默认值为`World`。这种写法的缺点在于,如果参数`y`赋值了,但是对应的布尔值为`false`,则该赋值不起作用。就像上面代码的最后一行,参数`y`等于空字符,结果被改为默认值。
+上面代码检查函数`log()`的参数`y`有没有赋值,如果没有,则指定默认值为`World`。这种写法的缺点在于,如果参数`y`赋值了,但是对应的布尔值为`false`,则该赋值不起作用。就像上面代码的最后一行,参数`y`等于空字符,结果被改为默认值。
为了避免这个问题,通常需要先判断一下参数`y`是否被赋值,如果没有,再等于默认值。
@@ -93,7 +93,7 @@ x = 100;
foo() // 101
```
-上面代码中,参数`p`的默认值是`x + 1`。这时,每次调用函数`foo`,都会重新计算`x + 1`,而不是默认`p`等于 100。
+上面代码中,参数`p`的默认值是`x + 1`。这时,每次调用函数`foo()`,都会重新计算`x + 1`,而不是默认`p`等于 100。
### 与解构赋值默认值结合使用
@@ -110,7 +110,7 @@ foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined
```
-上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数`foo`的参数是一个对象时,变量`x`和`y`才会通过解构赋值生成。如果函数`foo`调用时没提供参数,变量`x`和`y`就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。
+上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数`foo()`的参数是一个对象时,变量`x`和`y`才会通过解构赋值生成。如果函数`foo()`调用时没提供参数,变量`x`和`y`就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。
```javascript
function foo({x, y = 5} = {}) {
@@ -136,7 +136,7 @@ fetch('http://example.com')
// 报错
```
-上面代码中,如果函数`fetch`的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。
+上面代码中,如果函数`fetch()`的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。
```javascript
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
@@ -149,7 +149,19 @@ fetch('http://example.com')
上面代码中,函数`fetch`没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量`method`才会取到默认值`GET`。
-作为练习,请问下面两种写法有什么差别?
+注意,函数参数的默认值生效以后,参数解构赋值依然会进行。
+
+```javascript
+function f({ a, b = 'world' } = { a: 'hello' }) {
+ console.log(b);
+}
+
+f() // world
+```
+
+上面示例中,函数`f()`调用时没有参数,所以参数默认值`{ a: 'hello' }`生效,然后再对这个默认值进行解构赋值,从而触发参数变量`b`的默认值生效。
+
+作为练习,大家可以思考一下,下面两种函数写法有什么差别?
```javascript
// 写法一
@@ -161,11 +173,7 @@ function m1({x = 0, y = 0} = {}) {
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
-```
-上面两种写法都对函数的参数设定了默认值,区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
-
-```javascript
// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]
@@ -303,10 +311,10 @@ function foo(x = x) {
// ...
}
-foo() // ReferenceError: x is not defined
+foo() // ReferenceError: Cannot access 'x' before initialization
```
-上面代码中,参数`x = x`形成一个单独作用域。实际执行的是`let x = x`,由于暂时性死区的原因,这行代码会报错”x 未定义“。
+上面代码中,参数`x = x`形成一个单独作用域。实际执行的是`let x = x`,由于暂时性死区的原因,这行代码会报错。
如果参数的默认值是一个函数,该函数的作用域也遵守这个规则。请看下面的例子。
@@ -418,7 +426,7 @@ add(2, 5, 3) // 10
```javascript
// arguments变量的写法
function sortNumbers() {
- return Array.prototype.slice.call(arguments).sort();
+ return Array.from(arguments).sort();
}
// rest参数的写法
@@ -427,7 +435,7 @@ const sortNumbers = (...numbers) => numbers.sort();
上面代码的两种写法,比较后可以发现,rest 参数的写法更自然也更简洁。
-`arguments`对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用`Array.prototype.slice.call`先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组`push`方法的例子。
+`arguments`对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用`Array.from`先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组`push`方法的例子。
```javascript
function push(array, ...items) {
diff --git a/docs/generator.md b/docs/generator.md
index 7d18278..f93604c 100644
--- a/docs/generator.md
+++ b/docs/generator.md
@@ -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`。
@@ -552,26 +552,26 @@ g.throw(1);
上面代码中,`g.throw(1)`执行时,`next`方法一次都没有执行过。这时,抛出的错误不会被内部捕获,而是直接在外部抛出,导致程序出错。这种行为其实很好理解,因为第一次执行`next`方法,等同于启动执行 Generator 函数的内部代码,否则 Generator 函数还没有开始执行,这时`throw`方法抛错只可能抛出在函数外部。
-`throw`方法被捕获以后,会附带执行下一条`yield`表达式。也就是说,会附带执行一次`next`方法。
+`throw`方法被内部捕获以后,会附带执行到下一条`yield`表达式,这种情况下等同于执行一次`next`方法。
```javascript
var gen = function* gen(){
try {
- yield console.log('a');
+ yield 1;
} catch (e) {
- // ...
+ yield 2;
}
- yield console.log('b');
- yield console.log('c');
+ yield 3;
}
var g = gen();
-g.next() // a
-g.throw() // b
-g.next() // c
+g.next() // { value:1, done:false }
+g.throw() // { value:2, done:false }
+g.next() // { value:3, done:false }
+g.next() // { value:undefined, done:true }
```
-上面代码中,`g.throw`方法被捕获以后,自动执行了一次`next`方法,所以会打印`b`。另外,也可以看到,只要 Generator 函数内部部署了`try...catch`代码块,那么遍历器的`throw`方法抛出的错误,不影响下一次遍历。
+上面代码中,`g.throw`方法被内部捕获以后,等同于执行了一次`next`方法,所以返回`{ value:2, done:false }`。另外,也可以看到,只要 Generator 函数内部部署了`try...catch`代码块,那么遍历器的`throw`方法抛出的错误,不影响下一次遍历。
另外,`throw`命令与`g.throw`方法是无关的,两者互不影响。
diff --git a/docs/intro.md b/docs/intro.md
index 488a87b..ea16959 100644
--- a/docs/intro.md
+++ b/docs/intro.md
@@ -68,9 +68,9 @@ ES6 从开始制定到最后发布,整整用了 15 年。
2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。从 2000 年算起,这时已经过去了 15 年。
-目前,各大浏览器对 ES6 的支持可以查看[kangax.github.io/compat-table/es6/](https://kangax.github.io/compat-table/es6/)。
+目前,各大浏览器对 ES6 的支持可以查看[https://compat-table.github.io/compat-table/es6/](https://compat-table.github.io/compat-table/es6/)。
-Node.js 是 JavaScript 的服务器运行环境(runtime)。它对 ES6 的支持度更高。除了那些默认打开的功能,还有一些语法功能已经实现了,但是默认没有打开。使用下面的命令,可以查看 Node.js 默认没有打开的 ES6 实验性语法。
+Node.js 是 JavaScript 的服务器运行环境(runtime)。它对 ES6 的支持度更高。除了那些默认打开的功能,还有一些语法功能已经实现了,但是默认没有打开。使用下面的命令,可以查看 Node.js 默认没有打开的实验性语法。
```bash
// Linux & Mac
@@ -242,7 +242,7 @@ import 'core-js';
import 'regenerator-runtime/runtime';
// 或者
require('core-js');
-require('regenerator-runtime/runtime);
+require('regenerator-runtime/runtime');
```
Babel 默认不转码的 API 非常多,详细清单可以查看`babel-plugin-transform-runtime`模块的[definitions.js](https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-runtime/src/runtime-corejs3-definitions.js)文件。
diff --git a/docs/iterator.md b/docs/iterator.md
index 1bfcb7f..198ba2d 100644
--- a/docs/iterator.md
+++ b/docs/iterator.md
@@ -191,7 +191,7 @@ for (var value of range(0, 3)) {
上面代码是一个类部署 Iterator 接口的写法。`Symbol.iterator`属性对应一个函数,执行后返回当前对象的遍历器对象。
-下面是通过遍历器实现指针结构的例子。
+下面是通过遍历器实现“链表”结构的例子。
```javascript
function Obj(value) {
@@ -818,3 +818,43 @@ for (var n of fibonacci) {
```
上面的例子,会输出斐波纳契数列小于等于 1000 的项。如果当前项大于 1000,就会使用`break`语句跳出`for...of`循环。
+
+## 遍历器对象的工具方法
+
+ES2025 为遍历器接口返回的遍历器对象,添加了一些工具方法,便于处理数据。
+
+```javascript
+const arr = ['a', '', 'b', '', 'c', '', 'd', '', 'e'];
+
+arr.values() // creates an iterator
+ .filter(x => x.length > 0)
+ .drop(1)
+ .take(3)
+ .map(x => `=${x}=`)
+ .toArray()
+// ['=b=', '=c=', '=d=']
+```
+
+上面示例中,arr 是一个数组,它的 values() 方法返回的是一个遍历器对象,以前要使用 for...of 循环来处理,现在有了工具方法,就可以直接链式处理了。
+
+遍历器对象的工具方法,基本上与数组方法是对应的。
+
+- 返回遍历器对象的方法
+ - iterator.filter(filterFn)
+ - iterator.map(mapFn)
+ - iterator.flatMap(mapFn)
+- 返回布尔值的方法
+ - iterator.some(fn)
+ - iterator.every(fn)
+- 返回其他值的方法
+ - iterator.find(fn)
+ - iterator.reduce(reducer, initialValue?)
+- 不返回值的方法
+ - iterator.forEach(fn)
+
+以下是遍历器对象独有的方法。
+
+- iterator.drop(limit):返回一个遍历器对象,丢弃前 limit 个成员。
+- iterator.take(limit):返回一个遍历器对象,包含前 limit 个成员。
+- iterator.toArray():返回一个数组,包含所有成员。
+
diff --git a/docs/let.md b/docs/let.md
index 9bb8782..8a7c3ea 100644
--- a/docs/let.md
+++ b/docs/let.md
@@ -71,7 +71,7 @@ for (let i = 0; i < 3; i++) {
// abc
```
-上面代码正确运行,输出了 3 次`abc`。这表明函数内部的变量`i`与循环变量`i`不在同一个作用域,有各自单独的作用域。
+上面代码正确运行,输出了 3 次`abc`。这表明函数内部的变量`i`与循环变量`i`不在同一个作用域,有各自单独的作用域(同一个作用域不可使用 `let` 重复声明同一个变量)。
### 不存在变量提升
diff --git a/docs/module-loader.md b/docs/module-loader.md
index 22a2e50..31dd685 100644
--- a/docs/module-loader.md
+++ b/docs/module-loader.md
@@ -292,7 +292,7 @@ Node.js 要求 ES6 模块采用`.mjs`后缀文件名。也就是说,只要脚
}
```
-一旦设置了以后,该目录里面的 JS 脚本,就被解释用 ES6 模块。
+一旦设置了以后,该项目的 JS 脚本,就被解释成 ES6 模块。
```bash
# 解释成 ES6 模块
@@ -397,7 +397,7 @@ import submodule from './node_modules/es-module-package/private-module.js';
}
```
-由于`exports`字段只有支持 ES6 的 Node.js 才认识,所以可以用来兼容旧版本的 Node.js。
+由于`exports`字段只有支持 ES6 的 Node.js 才认识,所以可以搭配`main`字段,来兼容旧版本的 Node.js。
```javascript
{
@@ -412,7 +412,7 @@ import submodule from './node_modules/es-module-package/private-module.js';
**(3)条件加载**
-利用`.`这个别名,可以为 ES6 模块和 CommonJS 指定不同的入口。目前,这个功能需要在 Node.js 运行的时候,打开`--experimental-conditional-exports`标志。
+利用`.`这个别名,可以为 ES6 模块和 CommonJS 指定不同的入口。
```javascript
{
@@ -439,7 +439,7 @@ import submodule from './node_modules/es-module-package/private-module.js';
}
```
-注意,如果同时还有其他别名,就不能采用简写,否则或报错。
+注意,如果同时还有其他别名,就不能采用简写,否则会报错。
```javascript
{
diff --git a/docs/module.md b/docs/module.md
index 6c773c4..a5b1824 100644
--- a/docs/module.md
+++ b/docs/module.md
@@ -162,6 +162,8 @@ function f() {}
export {f};
```
+目前,export 命令能够对外输出的就是三种接口:函数(Functions), 类(Classes),var、let、const 声明的变量(Variables)。
+
另外,`export`语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
```javascript
@@ -556,9 +558,40 @@ import * as ns from "mod";
export {ns};
```
+## import 属性
+
+ES2025 引入了“[import 属性](https://github.com/tc39/proposal-import-attributes)”(import attributes),允许为 import 命令设置属性,主要用于导入非模块的代码,比如 JSON 数据、WebAssembly 代码、CSS 代码。
+
+目前,只支持导入 JSON 数据。
+
+```javascript
+// 静态导入
+import configData from './config-data.json' with { type: 'json' };
+
+// 动态导入
+const configData = await import(
+ './config-data.json', { with: { type: 'json' } }
+);
+```
+
+上面代码中,import 命令使用 with 子句,指定一个属性对象。这个属性对象目前只有一个 type 属性,它的值就是导入代码的类型,现在只能设置为`json`一个值。
+
+如果没有 import 属性,导入 JSON 数据只能使用 fetch 命令。
+
+```javascript
+const response = await fetch('./config.json');
+const json = await response.json();
+```
+
+export 命令与 import 命令写在一起,形成一个再导出语句时,也可以使用 import 属性。
+
+```javascript
+export { default as config } from './config-data.json' with { type: 'json' };
+```
+
## 模块的继承
-模块之间也可以继承。
+模块可以继承。
假设有一个`circleplus`模块,继承了`circle`模块。
@@ -692,7 +725,28 @@ import(`./section-modules/${someVariable}.js`)
});
```
-`import()`函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,`import()`函数与所加载的模块没有静态连接关系,这点也是与`import`语句不相同。`import()`类似于 Node 的`require`方法,区别主要是前者是异步加载,后者是同步加载。
+`import()`函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,`import()`函数与所加载的模块没有静态连接关系,这点也是与`import`语句不相同。`import()`类似于 Node.js 的`require()`方法,区别主要是前者是异步加载,后者是同步加载。
+
+由于`import()`返回 Promise
+对象,所以需要使用`then()`方法指定处理函数。考虑到代码的清晰,更推荐使用`await`命令。
+
+```javascript
+async function renderWidget() {
+ const container = document.getElementById('widget');
+ if (container !== null) {
+ // 等同于
+ // import("./widget").then(widget => {
+ // widget.render(container);
+ // });
+ const widget = await import('./widget.js');
+ widget.render(container);
+ }
+}
+
+renderWidget();
+```
+
+上面示例中,`await`命令后面就是使用`import()`,对比`then()`的写法明显更简洁易读。
### 适用场合
@@ -801,3 +855,45 @@ async function main() {
main();
```
+## import.meta
+
+开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。[ES2020](https://github.com/tc39/proposal-import-meta) 为 import 命令添加了一个元属性`import.meta`,返回当前模块的元信息。
+
+`import.meta`只能在模块内部使用,如果在模块外部使用会报错。
+
+这个属性返回一个对象,该对象的各种属性就是当前运行的脚本的元信息。具体包含哪些属性,标准没有规定,由各个运行环境自行决定。一般来说,`import.meta`至少会有下面两个属性。
+
+**(1)import.meta.url**
+
+`import.meta.url`返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是`https://foo.com/main.js`,`import.meta.url`就返回这个路径。如果模块里面还有一个数据文件`data.txt`,那么就可以用下面的代码,获取这个数据文件的路径。
+
+```javascript
+new URL('data.txt', import.meta.url)
+```
+
+注意,Node.js 环境中,`import.meta.url`返回的总是本地路径,即`file:URL`协议的字符串,比如`file:///home/user/foo.js`。
+
+**(2)import.meta.scriptElement**
+
+`import.meta.scriptElement`是浏览器特有的元属性,返回加载模块的那个`
+
+// my-module.js 内部执行下面的代码
+import.meta.scriptElement.dataset.foo
+// "abc"
+```
+
+**(3)其他**
+
+Deno 现在还支持`import.meta.filename`和`import.meta.dirname`属性,对应 CommonJS 模块系统的`__filename`和`__dirname`属性。
+
+- `import.meta.filename`:当前模块文件的绝对路径。
+- `import.meta.dirname`:当前模块文件的目录的绝对路径。
+
+这两个属性都提供当前平台的正确的路径分隔符,比如 Linux 系统返回`/dev/my_module.ts`,Windows 系统返回`C:\dev\my_module.ts`。
+
+本地模块可以使用这两个属性,远程模块也可以使用。
+
diff --git a/docs/number.md b/docs/number.md
index c21067e..820c03e 100644
--- a/docs/number.md
+++ b/docs/number.md
@@ -652,6 +652,21 @@ Math.hypot(-3); // 3
如果参数不是数值,`Math.hypot`方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。
+### Math.f16round()
+
+ES2025 新增了 Math.f16round() 方法,返回最接近输入值的16位半精度浮点数。
+
+```javascript
+Math.f16round(5) // 5
+Math.f16round(5.05) // 5.05078125
+```
+
+16位浮点数共使用16个二进制位,其中指数使用5位,符号位使用1位,精度使用10位,因此可以表示 ±65,504 范围内的值,精度可以到达 1/1024。如果一个数超出了值的范围,则该方法返回 infinity。
+
+```javascript
+Math.f16round(100000) // Infinity
+```
+
### 对数方法
ES6 新增了 4 个对数相关方法。
@@ -820,7 +835,7 @@ for (let i = 1; i <= 70; i++) {
console.log(p); // 1.197857166996989e+100
```
-现在支持大整数了,就可以算了,浏览器的开发者工具运行下面代码,就OK。
+现在支持大整数了,就可以算了,浏览器的开发者工具运行下面代码,就 OK。
```javascript
let p = 1n;
@@ -830,9 +845,9 @@ for (let i = 1n; i <= 70n; i++) {
console.log(p); // 11978571...00000000n
```
-### BigInt 对象
+### BigInt 函数
-JavaScript 原生提供`BigInt`对象,可以用作构造函数生成 BigInt 类型的数值。转换规则基本与`Number()`一致,将其他类型的值转为 BigInt。
+JavaScript 原生提供`BigInt`函数,可以用它生成 BigInt 类型的数值。转换规则基本与`Number()`一致,将其他类型的值转为 BigInt。
```javascript
BigInt(123) // 123n
@@ -841,7 +856,7 @@ BigInt(false) // 0n
BigInt(true) // 1n
```
-`BigInt()`构造函数必须有参数,而且参数必须可以正常转为数值,下面的用法都会报错。
+`BigInt()`函数必须有参数,而且参数必须可以正常转为数值,下面的用法都会报错。
```javascript
new BigInt() // TypeError
@@ -860,7 +875,7 @@ BigInt(1.5) // RangeError
BigInt('1.5') // SyntaxError
```
-BigInt 对象继承了 Object 对象的两个实例方法。
+BigInt 继承了 Object 对象的两个实例方法。
- `BigInt.prototype.toString()`
- `BigInt.prototype.valueOf()`
@@ -869,11 +884,10 @@ BigInt 对象继承了 Object 对象的两个实例方法。
- `BigInt.prototype.toLocaleString()`
-此外,还提供了三个静态方法。
+此外,还提供了两个静态方法。
- `BigInt.asUintN(width, BigInt)`: 给定的 BigInt 转为 0 到 2width - 1 之间对应的值。
- `BigInt.asIntN(width, BigInt)`:给定的 BigInt 转为 -2width - 1 到 2width - 1 - 1 之间对应的值。
-- `BigInt.parseInt(string[, radix])`:近似于`Number.parseInt()`,将一个字符串转换成指定进制的 BigInt。
```javascript
const max = 2n ** (64n - 1n) - 1n;
@@ -899,18 +913,6 @@ BigInt.asUintN(32, max) // 4294967295n
上面代码中,`max`是一个64位的 BigInt,如果转为32位,前面的32位都会被舍弃。
-下面是`BigInt.parseInt()`的例子。
-
-```javascript
-// Number.parseInt() 与 BigInt.parseInt() 的对比
-Number.parseInt('9007199254740993', 10)
-// 9007199254740992
-BigInt.parseInt('9007199254740993', 10)
-// 9007199254740993n
-```
-
-上面代码中,由于有效数字超出了最大限度,`Number.parseInt`方法返回的结果是不精确的,而`BigInt.parseInt`方法正确返回了对应的 BigInt。
-
对于二进制数组,BigInt 新增了两个类型`BigUint64Array`和`BigInt64Array`,这两种数据类型返回的都是64位 BigInt。`DataView`对象的实例方法`DataView.prototype.getBigInt64()`和`DataView.prototype.getBigUint64()`,返回的也是 BigInt。
### 转换规则
diff --git a/docs/object-methods.md b/docs/object-methods.md
index a9d8200..999496d 100644
--- a/docs/object-methods.md
+++ b/docs/object-methods.md
@@ -833,3 +833,31 @@ Object.fromEntries(map)
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }
```
+
+## Object.hasOwn()
+
+JavaScript 对象的属性分成两种:自身的属性和继承的属性。对象实例有一个`hasOwnProperty()`方法,可以判断某个属性是否为原生属性。ES2022 在`Object`对象上面新增了一个静态方法[`Object.hasOwn()`](https://github.com/tc39/proposal-accessible-object-hasownproperty),也可以判断是否为自身的属性。
+
+`Object.hasOwn()`可以接受两个参数,第一个是所要判断的对象,第二个是属性名。
+
+```javascript
+const foo = Object.create({ a: 123 });
+foo.b = 456;
+
+Object.hasOwn(foo, 'a') // false
+Object.hasOwn(foo, 'b') // true
+```
+
+上面示例中,对象`foo`的属性`a`是继承属性,属性`b`是原生属性。`Object.hasOwn()`对属性`a`返回`false`,对属性`b`返回`true`。
+
+`Object.hasOwn()`的一个好处是,对于不继承`Object.prototype`的对象不会报错,而`hasOwnProperty()`是会报错的。
+
+```javascript
+const obj = Object.create(null);
+
+obj.hasOwnProperty('foo') // 报错
+Object.hasOwn(obj, 'foo') // false
+```
+
+上面示例中,`Object.create(null)`返回的对象`obj`是没有原型的,不继承任何属性,这导致调用`obj.hasOwnProperty()`会报错,但是`Object.hasOwn()`就能正确处理这种情况。
+
diff --git a/docs/object.md b/docs/object.md
index d2965e8..5832a8f 100644
--- a/docs/object.md
+++ b/docs/object.md
@@ -606,6 +606,23 @@ foo
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
```
+对象的扩展运算符,只会返回参数对象自身的、可枚举的属性,这一点要特别小心,尤其是用于类的实例对象时。
+
+```javascript
+class C {
+ p = 12;
+ m() {}
+}
+
+let c = new C();
+let clone = { ...c };
+
+clone.p; // ok
+clone.m(); // 报错
+```
+
+上面示例中,`c`是`C`类的实例对象,对其进行扩展运算时,只会返回`c`自身的属性`c.p`,而不会返回`c`的方法`c.m()`,因为这个方法定义在`C`的原型对象上(详见 Class 的章节)。
+
对象的扩展运算符等同于使用`Object.assign()`方法。
```javascript
@@ -704,3 +721,77 @@ let aWithXGetter = { ...a }; // 报错
上面例子中,取值函数`get`在扩展`a`对象时会自动执行,导致报错。
+## AggregateError 错误对象
+
+ES2021 标准之中,为了配合新增的`Promise.any()`方法(参见《Promise 对象》一章),还引入一个新的错误对象`AggregateError`,也放在这一章介绍。
+
+AggregateError 在一个错误对象里面,封装了多个错误。如果某个单一操作,同时引发了多个错误,需要同时抛出这些错误,那么就可以抛出一个 AggregateError 错误对象,把各种错误都放在这个对象里面。
+
+AggregateError 本身是一个构造函数,用来生成 AggregateError 实例对象。
+
+```javascript
+AggregateError(errors[, message])
+```
+
+`AggregateError()`构造函数可以接受两个参数。
+
+- errors:数组,它的每个成员都是一个错误对象。该参数是必须的。
+- message:字符串,表示 AggregateError 抛出时的提示信息。该参数是可选的。
+
+```javascript
+const error = new AggregateError([
+ new Error('ERROR_11112'),
+ new TypeError('First name must be a string'),
+ new RangeError('Transaction value must be at least 1'),
+ new URIError('User profile link must be https'),
+], 'Transaction cannot be processed')
+```
+
+上面示例中,`AggregateError()`的第一个参数数组里面,一共有四个错误实例。第二个参数字符串则是这四个错误的一个整体的提示。
+
+`AggregateError`的实例对象有三个属性。
+
+- name:错误名称,默认为“AggregateError”。
+- message:错误的提示信息。
+- errors:数组,每个成员都是一个错误对象。
+
+下面是一个示例。
+
+```javascript
+try {
+ throw new AggregateError([
+ new Error("some error"),
+ ], 'Hello');
+} catch (e) {
+ console.log(e instanceof AggregateError); // true
+ console.log(e.message); // "Hello"
+ console.log(e.name); // "AggregateError"
+ console.log(e.errors); // [ Error: "some error" ]
+}
+```
+
+## Error 对象的 cause 属性
+
+Error 对象用来表示代码运行时的异常情况,但是从这个对象拿到的上下文信息,有时很难解读,也不够充分。[ES2022](https://github.com/tc39/proposal-error-cause) 为 Error 对象添加了一个`cause`属性,可以在生成错误时,添加报错原因的描述。
+
+它的用法是`new Error()`生成 Error 实例时,给出一个描述对象,该对象可以设置`cause`属性。
+
+```javascript
+const actual = new Error('an error!', { cause: 'Error cause' });
+actual.cause; // 'Error cause'
+```
+
+上面示例中,生成 Error 实例时,使用描述对象给出`cause`属性,写入报错的原因。然后,就可以从实例对象上读取这个属性。
+
+`cause`属性可以放置任意内容,不必一定是字符串。
+
+```javascript
+try {
+ maybeWorks();
+} catch (err) {
+ throw new Error('maybeWorks failed!', { cause: err });
+}
+```
+
+上面示例中,`cause`属性放置的就是一个对象。
+
diff --git a/docs/operator.md b/docs/operator.md
index e3958b4..fea9e90 100644
--- a/docs/operator.md
+++ b/docs/operator.md
@@ -298,7 +298,7 @@ function example(opts) {
}
```
-上面示例中,参数对象`opts`如果不存在属性`foo`和属性`bar`,则为这两个属性设置默认值。有了“Null 赋值运算符”以后,就可以统一写成下面这样。
+上面示例中,参数对象`opts`如果不存在属性`foo`和属性`baz`,则为这两个属性设置默认值。有了“Null 赋值运算符”以后,就可以统一写成下面这样。
```javascript
function example(opts) {
@@ -307,3 +307,45 @@ function example(opts) {
}
```
+## `#!`命令
+
+Unix 的命令行脚本都支持`#!`命令,又称为 Shebang 或 Hashbang。这个命令放在脚本的第一行,用来指定脚本的执行器。
+
+比如 Bash 脚本的第一行。
+
+```bash
+#!/bin/sh
+```
+
+Python 脚本的第一行。
+
+```python
+#!/usr/bin/env python
+```
+
+[ES2023](https://github.com/tc39/proposal-hashbang) 为 JavaScript 脚本引入了`#!`命令,写在脚本文件或者模块文件的第一行。
+
+```javascript
+// 写在脚本文件第一行
+#!/usr/bin/env node
+'use strict';
+console.log(1);
+
+// 写在模块文件第一行
+#!/usr/bin/env node
+export {};
+console.log(1);
+```
+
+有了这一行以后,Unix 命令行就可以直接执行脚本。
+
+```bash
+# 以前执行脚本的方式
+$ node hello.js
+
+# hashbang 的方式
+$ ./hello.js
+```
+
+对于 JavaScript 引擎来说,会把`#!`理解成注释,忽略掉这一行。
+
diff --git a/docs/promise.md b/docs/promise.md
index ea5b047..9f6b3fd 100644
--- a/docs/promise.md
+++ b/docs/promise.md
@@ -79,7 +79,7 @@ let promise = new Promise(function(resolve, reject) {
});
promise.then(function() {
- console.log('resolved.');
+ console.log('resolved');
});
console.log('Hi!');
@@ -691,7 +691,27 @@ p
## Promise.allSettled()
-`Promise.allSettled()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是`fulfilled`还是`rejected`,包装实例才会结束。该方法由 [ES2020](https://github.com/tc39/proposal-promise-allSettled) 引入。
+有时候,我们希望等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。但是,现有的 Promise 方法很难实现这个要求。
+
+`Promise.all()`方法只适合所有异步操作都成功的情况,如果有一个操作失败,就无法满足要求。
+
+```javascript
+const urls = [url_1, url_2, url_3];
+const requests = urls.map(x => fetch(x));
+
+try {
+ await Promise.all(requests);
+ console.log('所有请求都成功。');
+} catch {
+ console.log('至少一个请求失败,其他请求可能还没结束。');
+}
+```
+
+上面示例中,`Promise.all()`可以确定所有请求都成功了,但是只要有一个请求失败,它就会报错,而不管另外的请求是否结束。
+
+为了解决这个问题,[ES2020](https://github.com/tc39/proposal-promise-allSettled) 引入了`Promise.allSettled()`方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。
+
+`Promise.allSettled()`方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是`fulfilled`还是`rejected`),返回的 Promise 对象才会发生状态变更。
```javascript
const promises = [
@@ -704,9 +724,9 @@ await Promise.allSettled(promises);
removeLoadingIndicator();
```
-上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。
+上面示例中,数组`promises`包含了三个请求,只有等到这三个请求都结束了(不管请求成功还是失败),`removeLoadingIndicator()`才会执行。
-该方法返回的新的 Promise 实例,一旦结束,状态总是`fulfilled`,不会变成`rejected`。状态变成`fulfilled`后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入`Promise.allSettled()`的 Promise 实例。
+该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是`fulfilled`,不会变成`rejected`。状态变成`fulfilled`后,它的回调函数会接收到一个数组作为参数,该数组的每个成员对应前面数组的每个 Promise 对象。
```javascript
const resolved = Promise.resolve(42);
@@ -723,9 +743,21 @@ allSettledPromise.then(function (results) {
// ]
```
-上面代码中,`Promise.allSettled()`的返回值`allSettledPromise`,状态只可能变成`fulfilled`。它的监听函数接收到的参数是数组`results`。该数组的每个成员都是一个对象,对应传入`Promise.allSettled()`的两个 Promise 实例。每个对象都有`status`属性,该属性的值只可能是字符串`fulfilled`或字符串`rejected`。`fulfilled`时,对象有`value`属性,`rejected`时有`reason`属性,对应两种状态的返回值。
+上面代码中,`Promise.allSettled()`的返回值`allSettledPromise`,状态只可能变成`fulfilled`。它的回调函数接收到的参数是数组`results`。该数组的每个成员都是一个对象,对应传入`Promise.allSettled()`的数组里面的两个 Promise 对象。
+
+`results`的每个成员是一个对象,对象的格式是固定的,对应异步操作的结果。
+
+```javascript
+// 异步操作成功时
+{status: 'fulfilled', value: value}
+
+// 异步操作失败时
+{status: 'rejected', reason: reason}
+```
+
+成员对象的`status`属性的值只可能是字符串`fulfilled`或字符串`rejected`,用来区分异步操作是成功还是失败。如果是成功(`fulfilled`),对象会有`value`属性,如果是失败(`rejected`),会有`reason`属性,对应两种状态时前面异步操作的返回值。
-下面是返回值用法的例子。
+下面是返回值的用法例子。
```javascript
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
@@ -740,22 +772,6 @@ const errors = results
.map(p => p.reason);
```
-有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,`Promise.allSettled()`方法就很有用。如果没有这个方法,想要确保所有操作都结束,就很麻烦。`Promise.all()`方法无法做到这一点。
-
-```javascript
-const urls = [ /* ... */ ];
-const requests = urls.map(x => fetch(x));
-
-try {
- await Promise.all(requests);
- console.log('所有请求都成功。');
-} catch {
- console.log('至少一个请求失败,其他请求可能还没结束。');
-}
-```
-
-上面代码中,`Promise.all()`无法确定所有请求都结束。想要达到这个目的,写起来很麻烦,有了`Promise.allSettled()`,这就很容易了。
-
## Promise.any()
ES2021 引入了[`Promise.any()`方法](https://github.com/tc39/proposal-promise-any)。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
@@ -795,17 +811,7 @@ try {
上面代码中,`Promise.any()`方法的参数数组包含三个 Promise 操作。其中只要有一个变成`fulfilled`,`Promise.any()`返回的 Promise 对象就变成`fulfilled`。如果所有三个操作都变成`rejected`,那么`await`命令就会抛出错误。
-`Promise.any()`抛出的错误,不是一个一般的 Error 错误对象,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被`rejected`的操作所抛出的错误。下面是 AggregateError 的实现示例。
-
-```javascript
-// new AggregateError() extends Array
-
-const err = new AggregateError();
-err.push(new Error("first error"));
-err.push(new Error("second error"));
-// ...
-throw err;
-```
+`Promise.any()`抛出的错误是一个 AggregateError 实例(详见《对象的扩展》一章),这个 AggregateError 实例对象的`errors`属性是一个数组,包含了所有成员的错误。
下面是一个例子。
@@ -819,7 +825,8 @@ Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
- console.log(results); // [-1, Infinity]
+ console.log(results instanceof AggregateError); // true
+ console.log(results.errors); // [-1, Infinity]
});
```
@@ -1075,7 +1082,7 @@ console.log('next');
上面代码也是使用立即执行的匿名函数,执行`new Promise()`。这种情况下,同步函数也是同步执行的。
-鉴于这是一个很常见的需求,所以现在有一个[提案](https://github.com/ljharb/proposal-promise-try),提供`Promise.try`方法替代上面的写法。
+鉴于这是一个很常见的需求,所以 [ES2025](https://github.com/ljharb/proposal-promise-try) 提供了`Promise.try()`方法替代上面的写法。
```javascript
const f = () => console.log('now');
diff --git a/docs/proposals.md b/docs/proposals.md
index 4fb6848..f6178b5 100644
--- a/docs/proposals.md
+++ b/docs/proposals.md
@@ -314,21 +314,33 @@ const userAge = userId |> await fetchUserById |> getAgeFromUser;
const userAge = getAgeFromUser(await fetchUserById(userId));
```
+管道运算符对多步骤的数据处理,非常有用。
+
+```javascript
+const numbers = [10, 20, 30, 40, 50];
+
+const processedNumbers = numbers
+ |> (_ => _.map(n => n / 2)) // [5, 10, 15, 20, 25]
+ |> (_ => _.filter(n => n > 10)); // [15, 20, 25]
+```
+
+上面示例中,管道运算符可以清晰表达数据处理的每一步,增加代码的可读性。
+
## Math.signbit()
-`Math.sign()`用来判断一个值的正负,但是如果参数是`-0`,它会返回`-0`。
+JavaScript 内部使用64位浮点数(国际标准 IEEE 754)表示数值。IEEE 754 规定,64位浮点数的第一位是符号位,`0`表示正数,`1`表示负数。所以会有两种零,`+0`是符号位为`0`时的零,`-0`是符号位为`1`时的零。实际编程中,判断一个值是`+0`还是`-0`非常麻烦,因为它们是相等的。
```javascript
-Math.sign(-0) // -0
++0 === -0 // true
```
-这导致对于判断符号位的正负,`Math.sign()`不是很有用。JavaScript 内部使用 64 位浮点数(国际标准 IEEE 754)表示数值,IEEE 754 规定第一位是符号位,`0`表示正数,`1`表示负数。所以会有两种零,`+0`是符号位为`0`时的零值,`-0`是符号位为`1`时的零值。实际编程中,判断一个值是`+0`还是`-0`非常麻烦,因为它们是相等的。
+ES6 新增的`Math.sign()`方法,只能用来判断数值的正负,对于判断数值的符号位用处不大。因为如果参数是`-0`,它会返回`-0`,还是不能直接知道符号位是`1`还是`0`。
```javascript
-+0 === -0 // true
+Math.sign(-0) // -0
```
-目前,有一个[提案](http://jfbastien.github.io/papers/Math.signbit.html),引入了`Math.signbit()`方法判断一个数的符号位是否设置了。
+目前,有一个[提案](https://github.com/tc39/proposal-Math.signbit),引入了`Math.signbit()`方法判断一个数的符号位是否设置了。
```javascript
Math.signbit(2) //false
@@ -348,7 +360,7 @@ Math.signbit(-0) //true
## 双冒号运算符
-箭头函数可以绑定`this`对象,大大减少了显式绑定`this`对象的写法(`call`、`apply`、`bind`)。但是,箭头函数并不适用于所有场合,所以现在有一个[提案](https://github.com/zenparsing/es-function-bind),提出了“函数绑定”(function bind)运算符,用来取代`call`、`apply`、`bind`调用。
+箭头函数可以绑定`this`对象,大大减少了显式绑定`this`对象的写法(`call()`、`apply()`、`bind()`)。但是,箭头函数并不适用于所有场合,所以现在有一个[提案](https://github.com/zenparsing/es-function-bind),提出了“函数绑定”(function bind)运算符,用来取代`call()`、`apply()`、`bind()`调用。
函数绑定运算符是并排的两个冒号(`::`),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即`this`对象),绑定到右边的函数上面。
@@ -482,76 +494,3 @@ class FakeWindow extends Realm {
上面代码中,`FakeWindow`模拟了一个假的顶层对象`window`。
-## `#!`命令
-
-Unix 的命令行脚本都支持`#!`命令,又称为 Shebang 或 Hashbang。这个命令放在脚本的第一行,用来指定脚本的执行器。
-
-比如 Bash 脚本的第一行。
-
-```bash
-#!/bin/sh
-```
-
-Python 脚本的第一行。
-
-```python
-#!/usr/bin/env python
-```
-
-现在有一个[提案](https://github.com/tc39/proposal-hashbang),为 JavaScript 脚本引入了`#!`命令,写在脚本文件或者模块文件的第一行。
-
-```javascript
-// 写在脚本文件第一行
-#!/usr/bin/env node
-'use strict';
-console.log(1);
-
-// 写在模块文件第一行
-#!/usr/bin/env node
-export {};
-console.log(1);
-```
-
-有了这一行以后,Unix 命令行就可以直接执行脚本。
-
-```bash
-# 以前执行脚本的方式
-$ node hello.js
-
-# hashbang 的方式
-$ ./hello.js
-```
-
-对于 JavaScript 引擎来说,会把`#!`理解成注释,忽略掉这一行。
-
-## import.meta
-
-开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。现在有一个[提案](https://github.com/tc39/proposal-import-meta),为 import 命令添加了一个元属性`import.meta`,返回当前模块的元信息。
-
-`import.meta`只能在模块内部使用,如果在模块外部使用会报错。
-
-这个属性返回一个对象,该对象的各种属性就是当前运行的脚本的元信息。具体包含哪些属性,标准没有规定,由各个运行环境自行决定。一般来说,`import.meta`至少会有下面两个属性。
-
-**(1)import.meta.url**
-
-`import.meta.url`返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是`https://foo.com/main.js`,`import.meta.url`就返回这个路径。如果模块里面还有一个数据文件`data.txt`,那么就可以用下面的代码,获取这个数据文件的路径。
-
-```javascript
-new URL('data.txt', import.meta.url)
-```
-
-注意,Node.js 环境中,`import.meta.url`返回的总是本地路径,即是`file:URL`协议的字符串,比如`file:///home/user/foo.js`。
-
-**(2)import.meta.scriptElement**
-
-`import.meta.scriptElement`是浏览器特有的元属性,返回加载模块的那个`
-
-// my-module.js 内部执行下面的代码
-import.meta.scriptElement.dataset.foo
-// "abc"
-```
-
diff --git a/docs/proxy.md b/docs/proxy.md
index 33492c1..5885094 100644
--- a/docs/proxy.md
+++ b/docs/proxy.md
@@ -286,7 +286,7 @@ const proxy = new Proxy({}, {
proxy.getReceiver === proxy // true
```
-上面代码中,`proxy`对象的`getReceiver`属性是由`proxy`对象提供的,所以`receiver`指向`proxy`对象。
+上面代码中,`proxy`对象的`getReceiver`属性会被`get()`拦截,得到的返回值就是`proxy`对象。
```javascript
const proxy = new Proxy({}, {
@@ -1090,7 +1090,7 @@ target.m() // false
proxy.m() // true
```
-上面代码中,一旦`proxy`代理`target`,`target.m()`内部的`this`就是指向`proxy`,而不是`target`。
+上面代码中,一旦`proxy`代理`target`,`target.m()`内部的`this`就是指向`proxy`,而不是`target`。所以,虽然`proxy`没有做任何拦截,`target.m()`和`proxy.m()`返回不一样的结果。
下面是一个例子,由于`this`指向的变化,导致 Proxy 无法代理目标对象。
diff --git a/docs/reference.md b/docs/reference.md
index 909c21c..9c07274 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -184,7 +184,7 @@
## 异步操作和 Async 函数
-- Luke Hoban, [Async Functions for ECMAScript](https://github.com/lukehoban/ecmascript-asyncawait): Async 函数的设计思想,与 Promise、Gernerator 函数的关系
+- Luke Hoban, [Async Functions for ECMAScript](https://github.com/lukehoban/ecmascript-asyncawait): Async 函数的设计思想,与 Promise、Generator 函数的关系
- 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 混合使用的一些讨论
diff --git a/docs/regex.md b/docs/regex.md
index c148111..b8f03ad 100644
--- a/docs/regex.md
+++ b/docs/regex.md
@@ -376,7 +376,7 @@ JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先
```javascript
/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill') // ["100"]
-/(?a+)|(?b+)/v;
+```
+
+上面示例中,具名组匹配``在`|`前后使用了两次。
+
### 解构赋值和替换
有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。
@@ -594,54 +656,54 @@ RE_TWICE.test('abc!abc!abc') // true
RE_TWICE.test('abc!abc!ab') // false
```
-## 正则匹配索引
+## d 修饰符:正则匹配索引
-正则匹配结果的开始位置和结束位置,目前获取并不是很方便。正则实例的`exec()`方法,返回结果有一个`index`属性,可以获取整个匹配结果的开始位置,但是如果包含组匹配,每个组匹配的开始位置,很难拿到。
+组匹配的结果,在原始字符串里面的开始位置和结束位置,目前获取并不是很方便。正则实例的`exec()`方法有一个`index`属性,可以获取整个匹配结果的开始位置。但是,组匹配的每个组的开始位置,很难拿到。
-现在有一个[第三阶段提案](https://github.com/tc39/proposal-regexp-match-Indices),为`exec()`方法的返回结果加上`indices`属性,在这个属性上面可以拿到匹配的开始位置和结束位置。
+[ES2022](https://github.com/tc39/proposal-regexp-match-Indices) 新增了`d`修饰符,这个修饰符可以让`exec()`、`match()`的返回结果添加`indices`属性,在该属性上面可以拿到匹配的开始位置和结束位置。
```javascript
const text = 'zabbcdef';
-const re = /ab/;
+const re = /ab/d;
const result = re.exec(text);
result.index // 1
result.indices // [ [1, 3] ]
```
-上面例子中,`exec()`方法的返回结果`result`,它的`index`属性是整个匹配结果(`ab`)的开始位置,而它的`indices`属性是一个数组,成员是每个匹配的开始位置和结束位置的数组。由于该例子的正则表达式没有组匹配,所以`indices`数组只有一个成员,表示整个匹配的开始位置是`1`,结束位置是`3`。
+上面示例中,`exec()`方法的返回结果`result`,它的`index`属性是整个匹配结果(`ab`)的开始位置。由于正则表达式`re`有`d`修饰符,`result`现在就会多出一个`indices`属性。该属性是一个数组,它的每个成员还是一个数组,包含了匹配结果在原始字符串的开始位置和结束位置。由于上例的正则表达式`re`没有包含组匹配,所以`indices`数组只有一个成员,表示整个匹配的开始位置是`1`,结束位置是`3`。
-注意,开始位置包含在匹配结果之中,但是结束位置不包含在匹配结果之中。比如,匹配结果为`ab`,分别是原始字符串的第1位和第2位,那么结束位置就是第3位。
+注意,开始位置包含在匹配结果之中,相当于匹配结果的第一个字符的位置。但是,结束位置不包含在匹配结果之中,是匹配结果的下一个字符。比如,上例匹配结果的最后一个字符`b`的位置,是原始字符串的2号位,那么结束位置`3`就是下一个字符的位置。
如果正则表达式包含组匹配,那么`indices`属性对应的数组就会包含多个成员,提供每个组匹配的开始位置和结束位置。
```javascript
const text = 'zabbcdef';
-const re = /ab+(cd)/;
+const re = /ab+(cd)/d;
const result = re.exec(text);
result.indices // [ [ 1, 6 ], [ 4, 6 ] ]
```
-上面例子中,正则表达式包含一个组匹配,那么`indices`属性数组就有两个成员,第一个成员是整个匹配结果(`abbcd`)的开始位置和结束位置,第二个成员是组匹配(`cd`)的开始位置和结束位置。
+上面例子中,正则表达式`re`包含一个组匹配`(cd)`,那么`indices`属性数组就有两个成员,第一个成员是整个匹配结果(`abbcd`)的开始位置和结束位置,第二个成员是组匹配(`cd`)的开始位置和结束位置。
下面是多个组匹配的例子。
```javascript
const text = 'zabbcdef';
-const re = /ab+(cd(ef))/;
+const re = /ab+(cd(ef))/d;
const result = re.exec(text);
result.indices // [ [1, 8], [4, 8], [6, 8] ]
```
-上面例子中,正则表达式包含两个组匹配,所以`indices`属性数组就有三个成员。
+上面例子中,正则表达式`re`包含两个组匹配,所以`indices`属性数组就有三个成员。
如果正则表达式包含具名组匹配,`indices`属性数组还会有一个`groups`属性。该属性是一个对象,可以从该对象获取具名组匹配的开始位置和结束位置。
```javascript
const text = 'zabbcdef';
-const re = /ab+(?cd)/;
+const re = /ab+(?cd)/d;
const result = re.exec(text);
result.indices.groups // { Z: [ 4, 6 ] }
@@ -653,14 +715,14 @@ result.indices.groups // { Z: [ 4, 6 ] }
```javascript
const text = 'zabbcdef';
-const re = /ab+(?ce)?/;
+const re = /ab+(?ce)?/d;
const result = re.exec(text);
result.indices[1] // undefined
result.indices.groups['Z'] // undefined
```
-上面例子中,由于组匹配不成功,所以`indices`属性数组和`indices.groups`属性对象对应的组匹配成员都是`undefined`。
+上面例子中,由于组匹配`ce`不成功,所以`indices`属性数组和`indices.groups`属性对象对应的组匹配成员`Z`都是`undefined`。
## String.prototype.matchAll()
@@ -712,3 +774,70 @@ for (const match of string.matchAll(regex)) {
Array.from(string.matchAll(regex))
```
+## RegExp.escape()
+
+ES2025 添加了 RegExp.escape() 方法,它用来对字符串转义,使其可以安全地用于正则表达式。
+
+```javascript
+RegExp.escape('(*)')
+// '\\(\\*\\)'
+```
+
+上面示例中,原始字符串的三个字符`(`、`*`、`)`在正则表达式都有特殊含义,RegExp.escape() 可以对它们进行转义。
+
+注意,转义以后,每个特殊字符之前都加上了两个反斜杠。这是因为当该字符串用于正则表达式,字符串的转义机制会将两个反斜杠先转义成一个反斜杆,即`\\(`变成`\(`,从而正好用于正则表达式。
+
+没有特殊含义的字符,不会被转义。
+
+```javascript
+RegExp.escape('_abc123')
+// '_abc123'
+```
+
+该方法的经典用途是搜索和替换文本。
+
+```javascript
+function replacePlainText(str, searchText, replace) {
+ const searchRegExp = new RegExp(
+ RegExp.escape(searchText),
+ 'gu'
+ );
+ return str.replace(searchRegExp, replace)
+}
+```
+
+上面示例中,RegExp.escape() 先对用户输入的关键词进行转义,然后就可以将其当作正则表达式处理。
+
+## 组匹配修饰符
+
+ES2025 为组匹配添加了修饰符(inline flags),即修饰符只对正则表达式的一部分生效,对其他部分不生效。
+
+目前,组匹配只能使用下面三个修饰符。
+
+- i:忽略大小写
+- m:多行模式,即 ^ 和 $ 对每一行都生效。
+- s:dotAll 模式,即 . 可以匹配任何字符,包含每一行的终止符。
+
+```javascript
+/^x(?i:HELLO)x$/.test('xHELLOx')
+// true
+
+/^x(?i:HELLO)x$/.test('xhellox')
+// true
+```
+
+上面示例中,`(?i:HELLO)`表示 i 修饰符只用于组匹配`(HELLO)`,即`HELLO`不区分大小写。
+
+`(?flag:pattern)`是打开组匹配修饰符的写法,而`(?-flat:pattern)`是关闭组匹配修饰符的写法。
+
+```javascript
+/^x(?-i:HELLO)x$/i.test('xHELLOx')
+// true
+```
+
+上面示例中,整个正则表达式带有 i 修饰符,表示区分大小写,但是其中有一部分不需要区分,可以就可以使用`(?-i:HELLO)`对 HELLO 关闭区分大小写。
+
+如果需要对组匹配打开某些修饰符,同时关闭另一些修饰符,可以写成`(?flag-flag:pattern)`。同一个修饰符不能既打开,同时又关闭。
+
+另外,如果不带有修复符,那么`(?:pattern)`就是非捕获组匹配。
+
diff --git a/docs/set-map.md b/docs/set-map.md
index ac2ca3e..4407477 100644
--- a/docs/set-map.md
+++ b/docs/set-map.md
@@ -21,7 +21,7 @@ for (let i of s) {
上面代码通过`add()`方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值。
-`Set`函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
+`Set()`函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
```javascript
// 例一
@@ -88,6 +88,23 @@ set.size // 2
上面代码表示,由于两个空对象不相等,所以它们被视为两个值。
+`Array.from()`方法可以将 Set 结构转为数组。
+
+```javascript
+const items = new Set([1, 2, 3, 4, 5]);
+const array = Array.from(items);
+```
+
+这就提供了去除数组重复成员的另一种方法。
+
+```javascript
+function dedupe(array) {
+ return Array.from(new Set(array));
+}
+
+dedupe([1, 1, 2, 3]) // [1, 2, 3]
+```
+
### Set 实例的属性和方法
Set 结构的实例有以下属性。
@@ -114,11 +131,11 @@ s.has(1) // true
s.has(2) // true
s.has(3) // false
-s.delete(2);
+s.delete(2) // true
s.has(2) // false
```
-下面是一个对比,看看在判断是否包括一个键上面,`Object`结构和`Set`结构的写法不同。
+下面是一个对比,判断是否包括一个键,`Object`结构和`Set`结构写法的不同。
```javascript
// 对象的写法
@@ -142,23 +159,6 @@ if (properties.has(someName)) {
}
```
-`Array.from`方法可以将 Set 结构转为数组。
-
-```javascript
-const items = new Set([1, 2, 3, 4, 5]);
-const array = Array.from(items);
-```
-
-这就提供了去除数组重复成员的另一种方法。
-
-```javascript
-function dedupe(array) {
- return Array.from(new Set(array));
-}
-
-dedupe([1, 1, 2, 3]) // [1, 2, 3]
-```
-
### 遍历操作
Set 结构的实例有四个遍历方法,可以用于遍历成员。
@@ -302,23 +302,137 @@ set = new Set(Array.from(set, val => val * 2));
上面代码提供了两种方法,直接在遍历操作中改变原来的 Set 结构。
+### 集合运算
+
+[ES2025](https://github.com/tc39/proposal-set-methods) 为 Set 结构添加了以下集合运算方法。
+
+- Set.prototype.intersection(other):交集
+- Set.prototype.union(other):并集
+- Set.prototype.difference(other):差集
+- Set.prototype.symmetricDifference(other):对称差集
+- Set.prototype.isSubsetOf(other):判断是否为子集
+- Set.prototype.isSupersetOf(other):判断是否为超集
+- Set.prototype.isDisjointFrom(other):判断是否不相交
+
+以上方法的参数都必须是 Set 结构,或者是一个类似于 Set 的结构(拥有`size`属性,以及`keys()`和`has()`方法。
+
+`.union()`是并集运算,返回包含两个集合中存在的所有成员的集合。
+
+```javascript
+const frontEnd = new Set(["JavaScript", "HTML", "CSS"]);
+const backEnd = new Set(["Python", "Java", "JavaScript"]);
+
+const all = frontEnd.union(backEnd);
+// Set {"JavaScript", "HTML", "CSS", "Python", "Java"}
+```
+
+`.intersection()`是交集运算,返回同时包含在两个集合中的成员的集合。
+
+```javascript
+const frontEnd = new Set(["JavaScript", "HTML", "CSS"]);
+const backEnd = new Set(["Python", "Java", "JavaScript"]);
+
+const frontAndBackEnd = frontEnd.intersection(backEnd);
+// Set {"JavaScript"}
+```
+
+`.difference()`是差集运算,返回第一个集合中存在但第二个集合中不存在的所有成员的集合。
+
+```javascript
+const frontEnd = new Set(["JavaScript", "HTML", "CSS"]);
+const backEnd = new Set(["Python", "Java", "JavaScript"]);
+
+const onlyFrontEnd = frontEnd.difference(backEnd);
+// Set {"HTML", "CSS"}
+
+const onlyBackEnd = backEnd.difference(frontEnd);
+// Set {"Python", "Java"}
+```
+
+`.symmetryDifference()`是对称差集,返回两个集合的所有独一无二成员的集合,即去除了重复的成员。
+
+```javascript
+const frontEnd = new Set(["JavaScript", "HTML", "CSS"]);
+const backEnd = new Set(["Python", "Java", "JavaScript"]);
+
+const onlyFrontEnd = frontEnd.symmetricDifference(backEnd);
+// Set {"HTML", "CSS", "Python", "Java"}
+
+const onlyBackEnd = backEnd.symmetricDifference(frontEnd);
+// Set {"Python", "Java", "HTML", "CSS"}
+```
+
+注意,返回结果中的成员顺序,由添加到集合的顺序决定。
+
+`.isSubsetOf()`返回一个布尔值,判断第一个集合是否为第二个集合的子集,即第一个集合的所有成员都是第二个集合的成员。
+
+```javascript
+const frontEnd = new Set(["JavaScript", "HTML", "CSS"]);
+const declarative = new Set(["HTML", "CSS"]);
+
+declarative.isSubsetOf(frontEnd);
+// true
+
+frontEndLanguages.isSubsetOf(declarativeLanguages);
+// false
+```
+
+任何集合都是自身的子集。
+
+```javascript
+frontEnd.isSubsetOf(frontEnd);
+// true
+```
+
+`isSupersetOf()`返回一个布尔值,表示第一个集合是否为第二个集合的超集,即第二个集合的所有成员都是第一个集合的成员。
+
+```javascript
+const frontEnd = new Set(["JavaScript", "HTML", "CSS"]);
+const declarative = new Set(["HTML", "CSS"]);
+
+declarative.isSupersetOf(frontEnd);
+// false
+
+frontEnd.isSupersetOf(declarative);
+// true
+```
+
+任何集合都是自身的超集。
+
+```javascript
+frontEnd.isSupersetOf(frontEnd);
+// true
+```
+
+`.isDisjointFrom()`判断两个集合是否不相交,即没有共同成员。
+
+```javascript
+const frontEnd = new Set(["JavaScript", "HTML", "CSS"]);
+const interpreted = new Set(["JavaScript", "Ruby", "Python"]);
+const compiled = new Set(["Java", "C++", "TypeScript"]);
+
+interpreted.isDisjointFrom(compiled);
+// true
+
+frontEnd.isDisjointFrom(interpreted);
+// false
+```
+
## WeakSet
### 含义
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
-首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
+首先,WeakSet 的成员只能是对象和 Symbol 值,而不能是其他类型的值。
```javascript
const ws = new WeakSet();
-ws.add(1)
-// TypeError: Invalid value used in weak set
-ws.add(Symbol())
-// TypeError: invalid value used in weak set
+ws.add(1) // 报错
+ws.add(Symbol()) // 不报错
```
-上面代码试图向 WeakSet 添加一个数值和`Symbol`值,结果报错,因为 WeakSet 只能放置对象。
+上面代码试图向 WeakSet 添加一个数值和`Symbol`值,结果前者报错了,因为 WeakSet 只能放置对象和 Symbol 值。
其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
@@ -358,8 +472,8 @@ const ws = new WeakSet(b);
WeakSet 结构有以下三个方法。
-- **WeakSet.prototype.add(value)**:向 WeakSet 实例添加一个新成员。
-- **WeakSet.prototype.delete(value)**:清除 WeakSet 实例的指定成员。
+- **WeakSet.prototype.add(value)**:向 WeakSet 实例添加一个新成员,返回 WeakSet 结构本身。
+- **WeakSet.prototype.delete(value)**:清除 WeakSet 实例的指定成员,清除成功返回`true`,如果在 WeakSet 中找不到该成员或该成员不是对象,返回`false`。
- **WeakSet.prototype.has(value)**:返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
下面是一个例子。
@@ -373,10 +487,10 @@ ws.add(window);
ws.add(obj);
ws.has(window); // true
-ws.has(foo); // false
+ws.has(foo); // false
-ws.delete(window);
-ws.has(window); // false
+ws.delete(window); // true
+ws.has(window); // false
```
WeakSet 没有`size`属性,没有办法遍历它的成员。
@@ -633,7 +747,7 @@ m.has(undefined) // true
**(5)Map.prototype.delete(key)**
-`delete`方法删除某个键,返回`true`。如果删除失败,返回`false`。
+`delete()`方法删除某个键,返回`true`。如果删除失败,返回`false`。
```javascript
const m = new Map();
@@ -646,7 +760,7 @@ m.has(undefined) // false
**(6)Map.prototype.clear()**
-`clear`方法清除所有成员,没有返回值。
+`clear()`方法清除所有成员,没有返回值。
```javascript
let map = new Map();
@@ -928,19 +1042,16 @@ wm2.get(k2) // "bar"
`WeakMap`与`Map`的区别有两点。
-首先,`WeakMap`只接受对象作为键名(`null`除外),不接受其他类型的值作为键名。
+首先,`WeakMap`只接受对象(`null`除外)和 [Symbol 值](https://github.com/tc39/proposal-symbols-as-weakmap-keys)作为键名,不接受其他类型的值作为键名。
```javascript
const map = new WeakMap();
-map.set(1, 2)
-// TypeError: 1 is not an object!
-map.set(Symbol(), 2)
-// TypeError: Invalid value used as weak map key
-map.set(null, 2)
-// TypeError: Invalid value used as weak map key
+map.set(1, 2) // 报错
+map.set(null, 2) // 报错
+map.set(Symbol(), 2) // 不报错
```
-上面代码中,如果将数值`1`和`Symbol`值作为 WeakMap 的键名,都会报错。
+上面代码中,如果将数值`1`和`null`作为 WeakMap 的键名,都会报错,将 Symbol 值作为键名不会报错。
其次,`WeakMap`的键名所指向的对象,不计入垃圾回收机制。
@@ -1149,7 +1260,7 @@ let target = {};
let wr = new WeakRef(target);
```
-上面示例中,`target`是原始对象,构造函数`WeakRef()`创建了一个基于`target`的新对象`wr`。这里,`wr`就是一个 WeakRef 的示例,属于对`target`的弱引用,垃圾回收机制不会计入这个引用,也就是说,`wr`的引入不会妨碍原始对象`target`被垃圾回收机制清除。
+上面示例中,`target`是原始对象,构造函数`WeakRef()`创建了一个基于`target`的新对象`wr`。这里,`wr`就是一个 WeakRef 的实例,属于对`target`的弱引用,垃圾回收机制不会计入这个引用,也就是说,`wr`的引用不会妨碍原始对象`target`被垃圾回收机制清除。
WeakRef 实例对象有一个`deref()`方法,如果原始对象存在,该方法返回原始对象;如果原始对象已经被垃圾回收机制清除,该方法返回`undefined`。
@@ -1288,3 +1399,7 @@ class Thingy {
由于无法知道清理器何时会执行,所以最好避免使用它。另外,如果浏览器窗口关闭或者进程意外退出,清理器则不会运行。
+## 参考链接
+
+- [Union, intersection, difference, and more are coming to JavaScript Sets](https://www.sonarsource.com/blog/union-intersection-difference-javascript-sets/)
+
diff --git a/docs/string-methods.md b/docs/string-methods.md
index 2fbb761..1d3e63a 100644
--- a/docs/string-methods.md
+++ b/docs/string-methods.md
@@ -376,7 +376,7 @@ String.prototype.replaceAll(searchValue, replacement)
`replaceAll()`的第二个参数`replacement`是一个字符串,表示替换的文本,其中可以使用一些特殊字符串。
-- `$&`:匹配的子字符串。
+- `$&`:匹配的字符串。
- `` $` ``:匹配结果前面的文本。
- `$'`:匹配结果后面的文本。
- `$n`:匹配成功的第`n`组内容,`n`是从1开始的自然数。这个参数生效的前提是,第一个参数必须是正则表达式。
@@ -421,7 +421,7 @@ String.prototype.replaceAll(searchValue, replacement)
上面例子中,`replaceAll()`的第二个参数是一个函数,该函数的返回值会替换掉所有`b`的匹配。
-这个替换函数可以接受多个参数。第一个参数是捕捉到的匹配内容,第二个参数捕捉到是组匹配(有多少个组匹配,就有多少个对应的参数)。此外,最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置,最后一个参数是原字符串。
+这个替换函数可以接受多个参数。第一个参数是捕捉到的匹配内容,第二个参数是捕捉到的组匹配(有多少个组匹配,就有多少个对应的参数)。此外,最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置,最后一个参数是原字符串。
```javascript
const str = '123abc456';
@@ -437,3 +437,51 @@ str.replaceAll(regex, replacer)
上面例子中,正则表达式有三个组匹配,所以`replacer()`函数的第一个参数`match`是捕捉到的匹配内容(即字符串`123abc456`),后面三个参数`p1`、`p2`、`p3`则依次为三个组匹配。
+## 实例方法:at()
+
+`at()`方法接受一个整数作为参数,返回参数指定位置的字符,支持负索引(即倒数的位置)。
+
+```javascript
+const str = 'hello';
+str.at(1) // "e"
+str.at(-1) // "o"
+```
+
+如果参数位置超出了字符串范围,`at()`返回`undefined`。
+
+该方法来自数组添加的`at()`方法,目前还是一个第三阶段的提案,可以参考《数组》一章的介绍。
+
+## 实例方法:toWellFormed()
+
+ES2024 引入了新的字符串方法`toWellFormed()`,用来处理 Unicode 的代理字符对问题(surrogates)。
+
+JavaScript 语言内部使用 UTF-16 格式,表示每个字符。UTF-16 只有16位,只能表示码点在`U+0000`到`U+FFFF`之间的 Unicode 字符。对于码点大于`U+FFFF`的 Unicode 字符(即码点大于16位的字符,`U+10000`到`U+10FFFF`),解决办法是使用代理字符对,即用两个 UTF-16 字符组合表示。
+
+具体来说,UTF-16 规定,`U+D800`至`U+DFFF`是空字符段,专门留给代理字符对使用。只要遇到这个范围内的码点,就知道它是代理字符对,本身没有意义,必须两个字符结合在一起解读。其中,前一个字符的范围规定为`0xD800`到`0xDBFF`之间,后一个字符的范围规定为`0xDC00`到`0xDFFF`之间。举例来说,码点`U+1D306`对应的字符为`𝌆`,它写成 UTF-16 就是`0xD834 0xDF06`。
+
+但是,字符串里面可能会出现单个代理字符对,即`U+D800`至`U+DFFF`里面的字符,它没有配对的另一个字符,无法进行解读,导致出现各种状况。
+
+`.toWellFormed()`就是为了解决这个问题,不改变原始字符串,返回一个新的字符串,将原始字符串里面的单个代理字符对,都替换为`U+FFFD`,从而可以在任何正常处理字符串的函数里面使用。
+
+```javascript
+"ab\uD800".toWellFormed() // 'ab�'
+```
+
+上面示例中,`\uD800`是单个的代理字符对,单独使用时没有意义。`toWellFormed()`将这个字符转为`\uFFFD`。
+
+再看下面的例子,`encodeURI()`遇到单个的代理字符对,会报错。
+
+```javascript
+const illFormed = "https://example.com/search?q=\uD800";
+
+encodeURI(illFormed) // 报错
+```
+
+`toWellFormed()`将其转换格式后,再使用`encodeURI()`就不会报错了。
+
+```javascript
+const illFormed = "https://example.com/search?q=\uD800";
+
+encodeURI(illFormed.toWellFormed()) // 正确
+```
+
diff --git a/docs/string.md b/docs/string.md
index 3a3d785..c311b86 100644
--- a/docs/string.md
+++ b/docs/string.md
@@ -122,7 +122,7 @@ const PS = eval("'\u2029'");
根据标准,JSON 数据必须是 UTF-8 编码。但是,现在的`JSON.stringify()`方法有可能返回不符合 UTF-8 标准的字符串。
-具体来说,UTF-8 标准规定,`0xD800`到`0xDFFF`之间的码点,不能单独使用,必须配对使用。比如,`\uD834\uDF06`是两个码点,但是必须放在一起配对使用,代表字符`𝌆`。这是为了表示码点大于`0xFFFF`的字符的一种变通方法。单独使用`\uD834`和`\uDFO6`这两个码点是不合法的,或者颠倒顺序也不行,因为`\uDF06\uD834`并没有对应的字符。
+具体来说,UTF-8 标准规定,`0xD800`到`0xDFFF`之间的码点,不能单独使用,必须配对使用。比如,`\uD834\uDF06`是两个码点,但是必须放在一起配对使用,代表字符`𝌆`。这是为了表示码点大于`0xFFFF`的字符的一种变通方法。单独使用`\uD834`和`\uDF06`这两个码点是不合法的,或者颠倒顺序也不行,因为`\uDF06\uD834`并没有对应的字符。
`JSON.stringify()`的问题在于,它可能返回`0xD800`到`0xDFFF`之间的单个码点。
diff --git a/docs/style.md b/docs/style.md
index 42fd61d..95fe4c9 100644
--- a/docs/style.md
+++ b/docs/style.md
@@ -285,7 +285,7 @@ const boundMethod = (...params) => method.apply(this, params);
简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。
-所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。
+所有配置项都应该集中在一个对象,放在最后一个参数,布尔值最好不要直接作为参数,因为代码语义会很差,也不利于将来增加其他配置项。
```javascript
// bad
@@ -397,22 +397,24 @@ class PeekableQueue extends Queue {
## 模块
-首先,Module 语法是 JavaScript 模块的标准写法,坚持使用这种写法。使用`import`取代`require`。
+ES6 模块语法是 JavaScript 模块的标准写法,坚持使用这种写法,取代 Node.js 的 CommonJS 语法。
+
+首先,使用`import`取代`require()`。
```javascript
-// bad
+// CommonJS 的写法
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;
-// good
+// ES6 的写法
import { func1, func2 } from 'moduleA';
```
-使用`export`取代`module.exports`。
+其次,使用`export`取代`module.exports`。
```javascript
-// commonJS的写法
+// commonJS 的写法
var React = require('react');
var Breadcrumbs = React.createClass({
@@ -423,7 +425,7 @@ var Breadcrumbs = React.createClass({
module.exports = Breadcrumbs;
-// ES6的写法
+// ES6 的写法
import React from 'react';
class Breadcrumbs extends React.Component {
@@ -435,19 +437,9 @@ class Breadcrumbs extends React.Component {
export default Breadcrumbs;
```
-如果模块只有一个输出值,就使用`export default`,如果模块有多个输出值,就不使用`export default`,`export default`与普通的`export`不要同时使用。
-
-不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。
-
-```javascript
-// bad
-import * as myObject from './importModule';
-
-// good
-import myObject from './importModule';
-```
+如果模块只有一个输出值,就使用`export default`,如果模块有多个输出值,除非其中某个输出值特别重要,否则建议不要使用`export default`,即多个输出值如果是平等关系,`export default`与普通的`export`就不要同时使用。
-如果模块默认输出一个函数,函数名的首字母应该小写。
+如果模块默认输出一个函数,函数名的首字母应该小写,表示这是一个工具方法。
```javascript
function makeStyleGuide() {
@@ -456,7 +448,7 @@ function makeStyleGuide() {
export default makeStyleGuide;
```
-如果模块默认输出一个对象,对象名的首字母应该大写。
+如果模块默认输出一个对象,对象名的首字母应该大写,表示这是一个配置值对象。
```javascript
const StyleGuide = {
diff --git a/docs/symbol.md b/docs/symbol.md
index 75eb151..4693410 100644
--- a/docs/symbol.md
+++ b/docs/symbol.md
@@ -4,9 +4,9 @@
ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入`Symbol`的原因。
-ES6 引入了一种新的原始数据类型`Symbol`,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:`undefined`、`null`、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
+ES6 引入了一种新的原始数据类型`Symbol`,表示独一无二的值。它属于 JavaScript 语言的原生数据类型之一,其他数据类型是:`undefined`、`null`、布尔值(Boolean)、字符串(String)、数值(Number)、大整数(BigInt)、对象(Object)。
-Symbol 值通过`Symbol`函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
+Symbol 值通过`Symbol()`函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
```javascript
let s = Symbol();
@@ -17,9 +17,9 @@ typeof s
上面代码中,变量`s`就是一个独一无二的值。`typeof`运算符的结果,表明变量`s`是 Symbol 数据类型,而不是字符串之类的其他类型。
-注意,`Symbol`函数前不能使用`new`命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
+注意,`Symbol()`函数前不能使用`new`命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象,所以不能使用`new`命令来调用。另外,由于 Symbol 值不是对象,所以也不能添加属性。基本上,它是一种类似于字符串的数据类型。
-`Symbol`函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
+`Symbol()`函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述。这主要是为了在控制台显示,或者转为字符串时,比较容易区分。
```javascript
let s1 = Symbol('foo');
@@ -34,7 +34,7 @@ s2.toString() // "Symbol(bar)"
上面代码中,`s1`和`s2`是两个 Symbol 值。如果不加参数,它们在控制台的输出都是`Symbol()`,不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。
-如果 Symbol 的参数是一个对象,就会调用该对象的`toString`方法,将其转为字符串,然后才生成一个 Symbol 值。
+如果 Symbol 的参数是一个对象,就会调用该对象的`toString()`方法,将其转为字符串,然后才生成一个 Symbol 值。
```javascript
const obj = {
@@ -46,7 +46,7 @@ const sym = Symbol(obj);
sym // Symbol(abc)
```
-注意,`Symbol`函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的`Symbol`函数的返回值是不相等的。
+注意,`Symbol()`函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的`Symbol`函数的返回值是不相等的。
```javascript
// 没有参数的情况
@@ -62,7 +62,7 @@ let s2 = Symbol('foo');
s1 === s2 // false
```
-上面代码中,`s1`和`s2`都是`Symbol`函数的返回值,而且参数相同,但是它们是不相等的。
+上面代码中,`s1`和`s2`都是`Symbol()`函数的返回值,而且参数相同,但是它们是不相等的。事实上,如果调用100次`Symbol()`,会得到100个互不相等的值。
Symbol 值不能与其他类型的值进行运算,会报错。
@@ -101,13 +101,13 @@ sym + 2 // TypeError
## Symbol.prototype.description
-创建 Symbol 的时候,可以添加一个描述。
+前面说过,`Symbol()`函数创建 Symbol 值时,可以用参数添加一个描述。
```javascript
const sym = Symbol('foo');
```
-上面代码中,`sym`的描述就是字符串`foo`。
+上面代码中,`sym`这个值的描述就是字符串`foo`。
但是,读取这个描述需要将 Symbol 显式转为字符串,即下面的写法。
@@ -118,7 +118,7 @@ String(sym) // "Symbol(foo)"
sym.toString() // "Symbol(foo)"
```
-上面的用法不是很方便。[ES2019](https://github.com/tc39/proposal-Symbol-description) 提供了一个实例属性`description`,直接返回 Symbol 的描述。
+上面的用法不是很方便。[ES2019](https://github.com/tc39/proposal-Symbol-description) 提供了一个 Symbol 值的实例属性`description`,直接返回 Symbol 值的描述。
```javascript
const sym = Symbol('foo');
@@ -128,7 +128,7 @@ sym.description // "foo"
## 作为属性名的 Symbol
-由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
+由于每一个 Symbol 值都是不相等的,这意味着只要 Symbol 值作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
```javascript
let mySymbol = Symbol();
@@ -150,7 +150,7 @@ Object.defineProperty(a, mySymbol, { value: 'Hello!' });
a[mySymbol] // "Hello!"
```
-上面代码通过方括号结构和`Object.defineProperty`,将对象的属性名指定为一个 Symbol 值。
+上面代码通过方括号结构和`Object.defineProperty()`方法,将对象的属性名指定为一个 Symbol 值。
注意,Symbol 值作为对象属性名时,不能用点运算符。
@@ -280,7 +280,7 @@ const shapeType = {
## 属性名的遍历
-Symbol 作为属性名,遍历对象的时候,该属性不会出现在`for...in`、`for...of`循环中,也不会被`Object.keys()`、`Object.getOwnPropertyNames()`、`JSON.stringify()`返回。
+Symbol 值作为属性名,遍历对象的时候,该属性不会出现在`for...in`、`for...of`循环中,也不会被`Object.keys()`、`Object.getOwnPropertyNames()`、`JSON.stringify()`返回。
但是,它也不是私有属性,有一个`Object.getOwnPropertySymbols()`方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
@@ -846,7 +846,7 @@ String(obj) // 'str'
### Symbol.toStringTag
-对象的`Symbol.toStringTag`属性,指向一个方法。在该对象上面调用`Object.prototype.toString`方法时,如果这个属性存在,它的返回值会出现在`toString`方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制`[object Object]`或`[object Array]`中`object`后面的那个字符串。
+对象的`Symbol.toStringTag`属性,用来设定一个字符串(设为其他类型的值无效,但不报错)。在目标对象上面调用`Object.prototype.toString()`方法时,如果`Symbol.toStringTag`属性存在,该属性设定的字符串会出现在`toString()`方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制`[object Object]`或`[object Array]`中`object`后面的那个大写字符串。
```javascript
// 例一
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 6f5ca15..0000000
--- a/package-lock.json
+++ /dev/null
@@ -1,1587 +0,0 @@
-{
- "name": "es6-tutorial",
- "version": "1.0.0",
- "lockfileVersion": 1,
- "requires": true,
- "dependencies": {
- "@babel/code-frame": {
- "version": "7.12.11",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
- "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
- "requires": {
- "@babel/highlight": "^7.10.4"
- }
- },
- "@babel/helper-validator-identifier": {
- "version": "7.12.11",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
- "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
- },
- "@babel/highlight": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
- "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
- "requires": {
- "@babel/helper-validator-identifier": "^7.10.4",
- "chalk": "^2.0.0",
- "js-tokens": "^4.0.0"
- },
- "dependencies": {
- "ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "requires": {
- "color-convert": "^1.9.0"
- }
- },
- "chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "requires": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- }
- },
- "color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "requires": {
- "color-name": "1.1.3"
- }
- },
- "color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
- },
- "has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
- },
- "supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "requires": {
- "has-flag": "^3.0.0"
- }
- }
- }
- },
- "@iktakahiro/markdown-it-katex": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@iktakahiro/markdown-it-katex/-/markdown-it-katex-4.0.1.tgz",
- "integrity": "sha512-kGFooO7fIOgY34PSG8ZNVsUlKhhNoqhzW2kq94TNGa8COzh73PO4KsEoPOsQVG1mEAe8tg7GqG0FoVao0aMHaw==",
- "requires": {
- "katex": "^0.12.0"
- }
- },
- "@types/color-name": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
- "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
- },
- "@types/minimatch": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
- "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA=="
- },
- "@types/parse-json": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
- "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
- },
- "ansi-regex": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
- "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
- },
- "ansi-styles": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
- "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
- "requires": {
- "@types/color-name": "^1.1.1",
- "color-convert": "^2.0.1"
- }
- },
- "argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "requires": {
- "sprintf-js": "~1.0.2"
- }
- },
- "array-union": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
- "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
- "requires": {
- "array-uniq": "^1.0.1"
- }
- },
- "array-uniq": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
- "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY="
- },
- "async": {
- "version": "2.6.3",
- "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
- "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
- "requires": {
- "lodash": "^4.17.14"
- }
- },
- "at-least-node": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
- "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
- },
- "balanced-match": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
- "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
- },
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
- },
- "camel-case": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
- "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
- "requires": {
- "no-case": "^2.2.0",
- "upper-case": "^1.1.1"
- }
- },
- "chalk": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
- "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
- "requires": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- }
- },
- "ci-info": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
- "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="
- },
- "clean-css": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
- "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==",
- "requires": {
- "source-map": "~0.6.0"
- }
- },
- "cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
- "requires": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^7.0.0"
- }
- },
- "clone": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
- "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
- },
- "commander": {
- "version": "2.20.3",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
- },
- "commondir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
- },
- "compare-versions": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
- "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA=="
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
- },
- "connect": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
- "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
- "requires": {
- "debug": "2.6.9",
- "finalhandler": "1.1.2",
- "parseurl": "~1.3.3",
- "utils-merge": "1.0.1"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- }
- }
- }
- },
- "cosmiconfig": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz",
- "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==",
- "requires": {
- "@types/parse-json": "^4.0.0",
- "import-fresh": "^3.2.1",
- "parse-json": "^5.0.0",
- "path-type": "^4.0.0",
- "yaml": "^1.10.0"
- }
- },
- "date-format": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz",
- "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w=="
- },
- "debug": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
- "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
- "requires": {
- "ms": "2.1.2"
- },
- "dependencies": {
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- }
- }
- },
- "deepmerge": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
- "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
- },
- "depd": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
- },
- "destroy": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
- "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
- },
- "dom-serializer": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz",
- "integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==",
- "requires": {
- "domelementtype": "^2.0.1",
- "domhandler": "^4.0.0",
- "entities": "^2.0.0"
- },
- "dependencies": {
- "domhandler": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz",
- "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==",
- "requires": {
- "domelementtype": "^2.1.0"
- }
- }
- }
- },
- "domelementtype": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz",
- "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w=="
- },
- "domhandler": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz",
- "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==",
- "requires": {
- "domelementtype": "^2.0.1"
- }
- },
- "domutils": {
- "version": "2.4.4",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz",
- "integrity": "sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==",
- "requires": {
- "dom-serializer": "^1.0.1",
- "domelementtype": "^2.0.1",
- "domhandler": "^4.0.0"
- },
- "dependencies": {
- "domhandler": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz",
- "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==",
- "requires": {
- "domelementtype": "^2.1.0"
- }
- }
- }
- },
- "ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
- },
- "email-addresses": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz",
- "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg=="
- },
- "emoji-regex": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz",
- "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4="
- },
- "encodeurl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
- },
- "ensure-posix-path": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz",
- "integrity": "sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw=="
- },
- "entities": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
- "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
- },
- "error-ex": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
- "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
- "requires": {
- "is-arrayish": "^0.2.1"
- }
- },
- "escalade": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
- },
- "escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
- },
- "escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
- },
- "esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
- },
- "etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
- },
- "filename-reserved-regex": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz",
- "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q="
- },
- "filenamify": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz",
- "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=",
- "requires": {
- "filename-reserved-regex": "^1.0.0",
- "strip-outer": "^1.0.0",
- "trim-repeated": "^1.0.0"
- }
- },
- "filenamify-url": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz",
- "integrity": "sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=",
- "requires": {
- "filenamify": "^1.0.0",
- "humanize-url": "^1.0.0"
- }
- },
- "finalhandler": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
- "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
- "requires": {
- "debug": "2.6.9",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "on-finished": "~2.3.0",
- "parseurl": "~1.3.3",
- "statuses": "~1.5.0",
- "unpipe": "~1.0.0"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- }
- }
- }
- },
- "find-cache-dir": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
- "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==",
- "requires": {
- "commondir": "^1.0.1",
- "make-dir": "^3.0.2",
- "pkg-dir": "^4.1.0"
- }
- },
- "find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "requires": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- }
- },
- "find-versions": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz",
- "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==",
- "requires": {
- "semver-regex": "^3.1.2"
- }
- },
- "flatted": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
- "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA=="
- },
- "fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
- },
- "fs-extra": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
- "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
- "requires": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^4.0.0",
- "universalify": "^0.1.0"
- }
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
- },
- "get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
- },
- "gh-pages": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.1.0.tgz",
- "integrity": "sha512-3b1rly9kuf3/dXsT8+ZxP0UhNLOo1CItj+3e31yUVcaph/yDsJ9RzD7JOw5o5zpBTJVQLlJAASNkUfepi9fe2w==",
- "requires": {
- "async": "^2.6.1",
- "commander": "^2.18.0",
- "email-addresses": "^3.0.1",
- "filenamify-url": "^1.0.0",
- "find-cache-dir": "^3.3.1",
- "fs-extra": "^8.1.0",
- "globby": "^6.1.0"
- }
- },
- "github-slugger": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz",
- "integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==",
- "requires": {
- "emoji-regex": ">=6.0.0 <=6.1.1"
- }
- },
- "glob": {
- "version": "7.1.6",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
- "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "globby": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
- "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
- "requires": {
- "array-union": "^1.0.1",
- "glob": "^7.0.3",
- "object-assign": "^4.0.1",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0"
- }
- },
- "graceful-fs": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
- "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
- },
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
- },
- "he": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
- },
- "highlight.js": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.4.1.tgz",
- "integrity": "sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg=="
- },
- "html-minifier": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
- "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
- "requires": {
- "camel-case": "^3.0.0",
- "clean-css": "^4.2.1",
- "commander": "^2.19.0",
- "he": "^1.2.0",
- "param-case": "^2.1.1",
- "relateurl": "^0.2.7",
- "uglify-js": "^3.5.1"
- }
- },
- "html-to-text": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-6.0.0.tgz",
- "integrity": "sha512-r0KNC5aqCAItsjlgtirW6RW25c92Ee3ybQj8z//4Sl4suE3HIPqM4deGpYCUJULLjtVPEP1+Ma+1ZeX1iMsCiA==",
- "requires": {
- "deepmerge": "^4.2.2",
- "he": "^1.2.0",
- "htmlparser2": "^4.1.0",
- "lodash": "^4.17.20",
- "minimist": "^1.2.5"
- },
- "dependencies": {
- "lodash": {
- "version": "4.17.20",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
- "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
- }
- }
- },
- "htmlparser2": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
- "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
- "requires": {
- "domelementtype": "^2.0.1",
- "domhandler": "^3.0.0",
- "domutils": "^2.0.0",
- "entities": "^2.0.0"
- }
- },
- "http-errors": {
- "version": "1.7.3",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
- "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
- "requires": {
- "depd": "~1.1.2",
- "inherits": "2.0.4",
- "setprototypeof": "1.1.1",
- "statuses": ">= 1.5.0 < 2",
- "toidentifier": "1.0.0"
- }
- },
- "humanize-url": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz",
- "integrity": "sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=",
- "requires": {
- "normalize-url": "^1.0.0",
- "strip-url-auth": "^1.0.0"
- }
- },
- "husky": {
- "version": "4.3.8",
- "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz",
- "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==",
- "requires": {
- "chalk": "^4.0.0",
- "ci-info": "^2.0.0",
- "compare-versions": "^3.6.0",
- "cosmiconfig": "^7.0.0",
- "find-versions": "^4.0.0",
- "opencollective-postinstall": "^2.0.2",
- "pkg-dir": "^5.0.0",
- "please-upgrade-node": "^3.2.0",
- "slash": "^3.0.0",
- "which-pm-runs": "^1.0.0"
- },
- "dependencies": {
- "find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "requires": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- }
- },
- "locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "requires": {
- "p-locate": "^5.0.0"
- }
- },
- "p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "requires": {
- "yocto-queue": "^0.1.0"
- }
- },
- "p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "requires": {
- "p-limit": "^3.0.2"
- }
- },
- "pkg-dir": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
- "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==",
- "requires": {
- "find-up": "^5.0.0"
- }
- }
- }
- },
- "import-fresh": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
- "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
- "requires": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- }
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
- },
- "is-arrayish": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
- },
- "is-plain-obj": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
- "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
- },
- "js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
- },
- "js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
- "requires": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- }
- },
- "json-parse-even-better-errors": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
- },
- "jsonfile": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
- "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
- "requires": {
- "graceful-fs": "^4.1.6"
- }
- },
- "katex": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/katex/-/katex-0.12.0.tgz",
- "integrity": "sha512-y+8btoc/CK70XqcHqjxiGWBOeIL8upbS0peTPXTvgrh21n1RiWWcIpSWM+4uXq+IAgNh9YYQWdc7LVDPDAEEAg==",
- "requires": {
- "commander": "^2.19.0"
- }
- },
- "lines-and-columns": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
- "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA="
- },
- "linkify-it": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.2.tgz",
- "integrity": "sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ==",
- "requires": {
- "uc.micro": "^1.0.1"
- }
- },
- "locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "requires": {
- "p-locate": "^4.1.0"
- }
- },
- "lodash": {
- "version": "4.17.19",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
- "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
- },
- "log-symbols": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
- "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==",
- "requires": {
- "chalk": "^4.0.0"
- }
- },
- "log4js": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz",
- "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==",
- "requires": {
- "date-format": "^3.0.0",
- "debug": "^4.1.1",
- "flatted": "^2.0.1",
- "rfdc": "^1.1.4",
- "streamroller": "^2.2.4"
- }
- },
- "loppo": {
- "version": "0.6.23",
- "resolved": "https://registry.npmjs.org/loppo/-/loppo-0.6.23.tgz",
- "integrity": "sha512-PVlL8OH3Vwu8Rjm+S6DAPtFCgFW8SwfSlr+MAkTCL6iBrilQpcnFOp1t5pkwxPF6cLE5Z1Fm9IyzG2G/MeS0rQ==",
- "requires": {
- "connect": "^3.7.0",
- "debug": "^4.3.1",
- "fs-extra": "^9.0.1",
- "html-minifier": "4.x",
- "html-to-text": "6.x",
- "js-yaml": "^3.14.1",
- "lodash": "^4.17.20",
- "log-symbols": "4.x",
- "log4js": "^6.3.0",
- "loppo-theme-oceandeep": "2.x",
- "promptly": "^3.2.0",
- "serve-static": "^1.14.1",
- "tarim": "^0.1.2",
- "turpan": "^0.3.1",
- "walk-sync": "^2.2.0",
- "wordcount": "^1.1.1",
- "yargs": "^16.2.0"
- },
- "dependencies": {
- "fs-extra": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
- "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
- "requires": {
- "at-least-node": "^1.0.0",
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^1.0.0"
- }
- },
- "jsonfile": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
- "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
- "requires": {
- "graceful-fs": "^4.1.6",
- "universalify": "^2.0.0"
- },
- "dependencies": {
- "universalify": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
- "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
- }
- }
- },
- "lodash": {
- "version": "4.17.20",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
- "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
- },
- "universalify": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
- "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug=="
- }
- }
- },
- "loppo-theme-oceandeep": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/loppo-theme-oceandeep/-/loppo-theme-oceandeep-2.4.2.tgz",
- "integrity": "sha1-Mswjj72pUEpmZVTkbZdlh8LA9Ww="
- },
- "loppo-theme-wangdoc": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/loppo-theme-wangdoc/-/loppo-theme-wangdoc-0.5.2.tgz",
- "integrity": "sha512-IkRaTMb5rg4AUHIoLCOkv3zYtJtn7NnUui70c3uR0LHKJGRy2vNru+NjOOxsIAns1I8FnEew3F4Hktex+RiFxw=="
- },
- "lower-case": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
- "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
- },
- "make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
- "requires": {
- "semver": "^6.0.0"
- }
- },
- "markdown-it": {
- "version": "12.0.3",
- "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.0.3.tgz",
- "integrity": "sha512-M57RsMv+QQmJHz1yCu0gTJRMx/LlxRPtrrw+2kb/CpDVK/graCmWO0qfNnz/SE1FCNdyq3pkMMZ+itTnyT/YGA==",
- "requires": {
- "argparse": "^2.0.1",
- "entities": "~2.1.0",
- "linkify-it": "^3.0.1",
- "mdurl": "^1.0.1",
- "uc.micro": "^1.0.5"
- },
- "dependencies": {
- "argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
- }
- }
- },
- "markdown-it-abbr": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/markdown-it-abbr/-/markdown-it-abbr-1.0.4.tgz",
- "integrity": "sha1-1mtTZFIcuz3Yqlna37ovtoZcj9g="
- },
- "markdown-it-container": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-3.0.0.tgz",
- "integrity": "sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw=="
- },
- "markdown-it-deflist": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/markdown-it-deflist/-/markdown-it-deflist-2.1.0.tgz",
- "integrity": "sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg=="
- },
- "markdown-it-emoji": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.0.tgz",
- "integrity": "sha512-39j7/9vP/CPCKbEI44oV8yoPJTpvfeReTn/COgRhSpNrjWF3PfP/JUxxB0hxV6ynOY8KH8Y8aX9NMDdo6z+6YQ=="
- },
- "markdown-it-footnote": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-3.0.2.tgz",
- "integrity": "sha512-JVW6fCmZWjvMdDQSbOT3nnOQtd9iAXmw7hTSh26+v42BnvXeVyGMDBm5b/EZocMed2MbCAHiTX632vY0FyGB8A=="
- },
- "markdown-it-implicit-figures": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/markdown-it-implicit-figures/-/markdown-it-implicit-figures-0.10.0.tgz",
- "integrity": "sha512-1TWr6+apyoJvRa4Z7eIolZdeajZCRBcc1ckVXon7XwdL8MfydIWsHnZOS5zRrpUNX5b0/O9giWcmuItSkleK5A=="
- },
- "markdown-it-imsize": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/markdown-it-imsize/-/markdown-it-imsize-2.0.1.tgz",
- "integrity": "sha1-zKBCeQXQUziiR8ucqdloxc3dUXA="
- },
- "markdown-it-ins": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/markdown-it-ins/-/markdown-it-ins-3.0.0.tgz",
- "integrity": "sha512-+vyAdBuMGwmT2yMlAFJSx2VR/0QZ1onQ/Mkkmr4l9tDFOh5sVoAgRbkgbuSsk+sxJ9vaMH/IQ323ydfvQrPO/Q=="
- },
- "markdown-it-mark": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-3.0.0.tgz",
- "integrity": "sha512-HqMWeKfMMOu4zBO0emmxsoMWmbf2cPKZY1wP6FsTbKmicFfp5y4L3KXAsNeO1rM6NTJVOrNlLKMPjWzriBGspw=="
- },
- "markdown-it-sub": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz",
- "integrity": "sha1-N1/WAm6ufdywEkl/ZBEZXqHjr+g="
- },
- "markdown-it-sup": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz",
- "integrity": "sha1-y5yf+RpSVawI8/09YyhuFd8KH8M="
- },
- "markdown-it-task-lists": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz",
- "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA=="
- },
- "match-words": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/match-words/-/match-words-0.1.1.tgz",
- "integrity": "sha1-3NMRnnSmpnvkMEylz5JlgOjhp68=",
- "requires": {
- "word-regex": "^0.1.0"
- }
- },
- "matcher-collection": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz",
- "integrity": "sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==",
- "requires": {
- "@types/minimatch": "^3.0.3",
- "minimatch": "^3.0.2"
- }
- },
- "mdurl": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
- "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
- },
- "mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
- },
- "minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "minimist": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- },
- "mute-stream": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
- "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
- },
- "no-case": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
- "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
- "requires": {
- "lower-case": "^1.1.1"
- }
- },
- "normalize-url": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
- "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",
- "requires": {
- "object-assign": "^4.0.1",
- "prepend-http": "^1.0.0",
- "query-string": "^4.1.0",
- "sort-keys": "^1.0.0"
- }
- },
- "object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
- },
- "on-finished": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
- "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
- "requires": {
- "ee-first": "1.1.1"
- }
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "requires": {
- "wrappy": "1"
- }
- },
- "opencollective-postinstall": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
- "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q=="
- },
- "p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "requires": {
- "p-try": "^2.0.0"
- }
- },
- "p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "requires": {
- "p-limit": "^2.2.0"
- }
- },
- "p-try": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
- "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
- },
- "param-case": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
- "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
- "requires": {
- "no-case": "^2.2.0"
- }
- },
- "parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "requires": {
- "callsites": "^3.0.0"
- }
- },
- "parse-json": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
- "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
- "requires": {
- "@babel/code-frame": "^7.0.0",
- "error-ex": "^1.3.1",
- "json-parse-even-better-errors": "^2.3.0",
- "lines-and-columns": "^1.1.6"
- }
- },
- "parseurl": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
- },
- "path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
- },
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
- },
- "path-type": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
- "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
- },
- "pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
- },
- "pinkie": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
- "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
- },
- "pinkie-promise": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
- "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
- "requires": {
- "pinkie": "^2.0.0"
- }
- },
- "pkg-dir": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
- "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
- "requires": {
- "find-up": "^4.0.0"
- }
- },
- "please-upgrade-node": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
- "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==",
- "requires": {
- "semver-compare": "^1.0.0"
- }
- },
- "prepend-http": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
- "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
- },
- "promptly": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/promptly/-/promptly-3.2.0.tgz",
- "integrity": "sha512-WnR9obtgW+rG4oUV3hSnNGl1pHm3V1H/qD9iJBumGSmVsSC5HpZOLuu8qdMb6yCItGfT7dcRszejr/5P3i9Pug==",
- "requires": {
- "read": "^1.0.4"
- }
- },
- "query-string": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
- "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
- "requires": {
- "object-assign": "^4.1.0",
- "strict-uri-encode": "^1.0.0"
- }
- },
- "range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
- },
- "read": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
- "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=",
- "requires": {
- "mute-stream": "~0.0.4"
- }
- },
- "relateurl": {
- "version": "0.2.7",
- "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
- "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
- },
- "require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
- },
- "resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
- },
- "rfdc": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
- "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug=="
- },
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
- },
- "semver-compare": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
- "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w="
- },
- "semver-regex": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.2.tgz",
- "integrity": "sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA=="
- },
- "send": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
- "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
- "requires": {
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "destroy": "~1.0.4",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "~1.7.2",
- "mime": "1.6.0",
- "ms": "2.1.1",
- "on-finished": "~2.3.0",
- "range-parser": "~1.2.1",
- "statuses": "~1.5.0"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- },
- "dependencies": {
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- }
- }
- },
- "ms": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
- }
- }
- },
- "serve-static": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
- "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
- "requires": {
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "0.17.1"
- }
- },
- "setprototypeof": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
- "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
- },
- "slash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
- },
- "sort-keys": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
- "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
- "requires": {
- "is-plain-obj": "^1.0.0"
- }
- },
- "source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
- },
- "sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
- },
- "statuses": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
- "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
- },
- "streamroller": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz",
- "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==",
- "requires": {
- "date-format": "^2.1.0",
- "debug": "^4.1.1",
- "fs-extra": "^8.1.0"
- },
- "dependencies": {
- "date-format": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
- "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA=="
- }
- }
- },
- "strict-uri-encode": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
- "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
- },
- "string-width": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
- "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.0"
- },
- "dependencies": {
- "emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
- }
- }
- },
- "strip-ansi": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
- "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
- "requires": {
- "ansi-regex": "^5.0.0"
- }
- },
- "strip-outer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
- "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
- "requires": {
- "escape-string-regexp": "^1.0.2"
- }
- },
- "strip-url-auth": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz",
- "integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164="
- },
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "requires": {
- "has-flag": "^4.0.0"
- }
- },
- "tarim": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/tarim/-/tarim-0.1.3.tgz",
- "integrity": "sha512-VPB0U1YV1fBAmADwuTDVKCqEeSJmzuZvl53CyWOOJCWxp2BsHNnGLX5VuginSTUjBuD3LC2Tkv5JJDYi4iv8fA==",
- "requires": {
- "fs-extra": "8.x",
- "lodash": "^4.17.14"
- }
- },
- "toidentifier": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
- "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
- },
- "trim-repeated": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
- "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",
- "requires": {
- "escape-string-regexp": "^1.0.2"
- }
- },
- "turpan": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/turpan/-/turpan-0.3.1.tgz",
- "integrity": "sha512-jL6AMHTA2yExzbSoZTRinbWPzf9nvQfVFk0V6JlBTIbPCKMbAfgXRFHZuOFe6ZXm6+LE6s3jypNRNXkAY2yKaw==",
- "requires": {
- "@iktakahiro/markdown-it-katex": "^4.0.1",
- "clone": "^2.1.1",
- "github-slugger": "^1.1.1",
- "highlight.js": "^10.4.1",
- "markdown-it": "^12.0.1",
- "markdown-it-abbr": "^1.0.4",
- "markdown-it-container": "^3.0.0",
- "markdown-it-deflist": "^2.0.1",
- "markdown-it-emoji": "^2.0.0",
- "markdown-it-footnote": "^3.0.1",
- "markdown-it-implicit-figures": "^0.10.0",
- "markdown-it-imsize": "^2.0.1",
- "markdown-it-ins": "^3.0.0",
- "markdown-it-mark": "^3.0.0",
- "markdown-it-sub": "^1.0.0",
- "markdown-it-sup": "^1.0.0",
- "markdown-it-task-lists": "^2.1.1",
- "yargs": "^16.1.1"
- }
- },
- "uc.micro": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
- "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
- },
- "uglify-js": {
- "version": "3.12.2",
- "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.2.tgz",
- "integrity": "sha512-rWYleAvfJPjduYCt+ELvzybNah/zIkRteGXIBO8X0lteRZPGladF61hFi8tU7qKTsF7u6DUQCtT9k00VlFOgkg=="
- },
- "universalify": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
- "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
- },
- "unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
- },
- "upper-case": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
- "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
- },
- "utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
- },
- "walk-sync": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.2.0.tgz",
- "integrity": "sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg==",
- "requires": {
- "@types/minimatch": "^3.0.3",
- "ensure-posix-path": "^1.1.0",
- "matcher-collection": "^2.0.0",
- "minimatch": "^3.0.4"
- }
- },
- "which-pm-runs": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
- "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
- },
- "word-regex": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/word-regex/-/word-regex-0.1.2.tgz",
- "integrity": "sha1-o7x/LSIs5Kk8JGw+9pRY9h9RFjk="
- },
- "wordcount": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/wordcount/-/wordcount-1.1.1.tgz",
- "integrity": "sha1-5y2ngzkE2HChVMnvR+1fPnmXz1c=",
- "requires": {
- "match-words": "^0.1.0"
- }
- },
- "wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "requires": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- }
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
- },
- "y18n": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
- "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
- },
- "yaml": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
- "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg=="
- },
- "yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
- "requires": {
- "cliui": "^7.0.2",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- }
- },
- "yargs-parser": {
- "version": "20.2.4",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
- "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA=="
- },
- "yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
- }
- }
-}
diff --git a/package.json b/package.json
index b0e13c0..97bde07 100644
--- a/package.json
+++ b/package.json
@@ -13,11 +13,6 @@
"chapter": "loppo chapter",
"server": "loppo server"
},
- "husky": {
- "hooks": {
- "pre-push": "npm update"
- }
- },
"keywords": [
"wangdoc",
"es6",
@@ -26,9 +21,8 @@
"author": "Ruan yifeng",
"license": "ICC BY-SA 4.0",
"dependencies": {
- "gh-pages": "^3.x",
- "husky": "^4.3.8",
- "loppo": "^0.6.23",
- "loppo-theme-wangdoc": "^0.5.2"
+ "gh-pages": "6.x",
+ "loppo": "^0.6.24",
+ "loppo-theme-wangdoc": "^0.7.3"
}
}