1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 18:32:22 +00:00

Merge branch 'gh-pages' into banner

This commit is contained in:
ruanyf 2019-10-12 17:18:48 +08:00
commit e2d3b00559
11 changed files with 268 additions and 44 deletions

View File

@ -32,7 +32,7 @@ const numbers = [4, 38];
add(...numbers) // 42 add(...numbers) // 42
``` ```
上面代码中,`array.push(...items)``add(...numbers)`这两行,都是函数的调用,它们都使用了扩展运算符。该运算符将一个数组,变为参数序列。 上面代码中,`array.push(...items)``add(...numbers)`这两行,都是函数的调用,它们都使用了扩展运算符。该运算符将一个数组,变为参数序列。
扩展运算符与正常的函数参数可以结合使用,非常灵活。 扩展运算符与正常的函数参数可以结合使用,非常灵活。
@ -947,3 +947,43 @@ for (let i of arr) {
``` ```
由于空位的处理规则非常不统一,所以建议避免出现空位。 由于空位的处理规则非常不统一,所以建议避免出现空位。
## Array.prototype.sort() 的排序稳定性
排序稳定性stable sorting是排序算法的重要属性指的是排序关键字相同的项目排序前后的顺序不变。
```javascript
const arr = [
'peach',
'straw',
'apple',
'spork'
];
const stableSorting = (s1, s2) => {
if (s1[0] < s2[0]) return -1;
return 1;
};
arr.sort(stableSorting)
// ["apple", "peach", "straw", "spork"]
```
上面代码对数组`arr`按照首字母进行排序。排序结果中,`straw``spork`的前面,跟原始顺序一致,所以排序算法`stableSorting`是稳定排序。
```javascript
const unstableSorting = (s1, s2) => {
if (s1[0] <= s2[0]) return -1;
return 1;
};
arr.sort(unstableSorting)
// ["apple", "peach", "spork", "straw"]
```
上面代码中,排序结果是`spork``straw`前面,跟原始顺序相反,所以排序算法`unstableSorting`是不稳定的。
常见的排序算法之中,插入排序、合并排序、冒泡排序等都是稳定的,堆排序、快速排序等是不稳定的。不稳定排序的主要缺点是,多重排序时可能会产生问题。假设有一个姓和名的列表,按照“先姓,后名”进行排序。开发者可能会先按名字排序,再按姓氏进行排序。如果排序算法是稳定的,这样也可以达到“先姓,后名”的排序效果。如果是不稳定的,就不行。
早先的 ECMAScript 没有规定,`Array.prototype.sort()`的默认排序算法是否稳定,留给浏览器自己决定,这导致某些实现是不稳定的。[ES2019](https://github.com/tc39/ecma262/pull/1340) 明确规定,`Array.prototype.sort()`的默认排序算法必须稳定。这个规定已经做到了,现在 JavaScript 各个主要实现的默认排序算法都是稳定的。

View File

@ -681,7 +681,7 @@ Foo.prop // 1
上面的写法为`Foo`类定义了一个静态属性`prop` 上面的写法为`Foo`类定义了一个静态属性`prop`
目前,只有这种写法可行,因为 ES6 明确规定Class 内部只有静态方法,没有静态属性。现在有一个[提案](https://github.com/tc39/proposal-class-fields)提供了类的静态属性,写法是在实例属性的前面,加上`static`关键字。 目前,只有这种写法可行,因为 ES6 明确规定Class 内部只有静态方法,没有静态属性。现在有一个[提案](https://github.com/tc39/proposal-class-fields)提供了类的静态属性,写法是在实例属性的前面,加上`static`关键字。
```javascript ```javascript
class MyClass { class MyClass {

View File

@ -700,7 +700,7 @@ g.next() // { value: 1, done: false }
g.return() // { value: undefined, done: true } g.return() // { value: undefined, done: true }
``` ```
如果 Generator 函数内部有`try...finally`代码块,且正在执行`try`代码块,那么`return`方法会推迟到`finally`代码块执行完再执行 如果 Generator 函数内部有`try...finally`代码块,且正在执行`try`代码块,那么`return`方法会导致立刻进入`finally`代码块,执行完以后,整个函数才会结束
```javascript ```javascript
function* numbers () { function* numbers () {
@ -722,7 +722,7 @@ g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true } g.next() // { value: 7, done: true }
``` ```
上面代码中,调用`return`方法后,就开始执行`finally`代码块,然后等到`finally`代码块执行完,再执行`return`方法 上面代码中,调用`return()`方法后,就开始执行`finally`代码块,不执行`try`里面剩下的代码了,然后等到`finally`代码块执行完,再返回`return()`方法指定的返回值
## next()、throw()、return() 的共同点 ## next()、throw()、return() 的共同点

View File

@ -221,7 +221,7 @@ import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作 a.foo = 'hello'; // 合法操作
``` ```
上面代码中,`a`的属性可以成功改写,并且其他模块也可以读到改写后的值。不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,轻易不要改变它的属性。 上面代码中,`a`的属性可以成功改写,并且其他模块也可以读到改写后的值。不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,不要轻易改变它的属性。
`import`后面的`from`指定模块文件的位置,可以是相对路径,也可以是绝对路径,`.js`后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。 `import`后面的`from`指定模块文件的位置,可以是相对路径,也可以是绝对路径,`.js`后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。

View File

@ -4,7 +4,7 @@
## 属性的简洁表示法 ## 属性的简洁表示法
ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。 ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
```javascript ```javascript
const foo = 'bar'; const foo = 'bar';
@ -15,7 +15,7 @@ baz // {foo: "bar"}
const baz = {foo: foo}; const baz = {foo: foo};
``` ```
上面代码表明ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。下面是另一个例子。 上面代码中,变量`foo`直接写在大括号里面。这时,属性名就是变量名, 属性值就是变量值。下面是另一个例子。
```javascript ```javascript
function f(x, y) { function f(x, y) {
@ -125,31 +125,24 @@ const cart = {
} }
``` ```
注意,简洁写法的属性名总是字符串,这会导致一些看上去比较奇怪的结果 简洁写法在打印对象时也很有用
```javascript ```javascript
const obj = { let user = {
class () {} name: 'test'
}; };
// 等同于 let foo = {
bar: 'baz'
var obj = {
'class': function() {}
}; };
console.log(user, foo)
// {name: "test"} {bar: "baz"}
console.log({user, foo})
// {user: {name: "test"}, foo: {bar: "baz"}}
``` ```
上面代码中,`class`是字符串,所以不会因为它属于关键字,而导致语法解析报错。 上面代码中,`console.log`直接输出`user``foo`两个对象时,就是两组键值对,可能会混淆。把它们放在大括号里面输出,就变成了对象的简洁表示法,每组键值对前面会打印对象名,这样就比较清晰了。
如果某个方法的值是一个 Generator 函数,前面需要加上星号。
```javascript
const obj = {
* m() {
yield 'hello world';
}
};
```
## 属性名表达式 ## 属性名表达式

View File

@ -567,13 +567,13 @@ Promise.reject(3).finally(() => {})
## Promise.all() ## Promise.all()
`Promise.all`方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 `Promise.all()`方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
```javascript ```javascript
const p = Promise.all([p1, p2, p3]); const p = Promise.all([p1, p2, p3]);
``` ```
上面代码中,`Promise.all`方法接受一个数组作为参数,`p1``p2``p3`都是 Promise 实例,如果不是,就会先调用下面讲到的`Promise.resolve`方法,将参数转为 Promise 实例,再进一步处理。`Promise.all`方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。 上面代码中,`Promise.all()`方法接受一个数组作为参数,`p1``p2``p3`都是 Promise 实例,如果不是,就会先调用下面讲到的`Promise.resolve`方法,将参数转为 Promise 实例,再进一步处理。另外,`Promise.all()`方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
`p`的状态由`p1``p2``p3`决定,分成两种情况。 `p`的状态由`p1``p2``p3`决定,分成两种情况。
@ -662,7 +662,7 @@ Promise.all([p1, p2])
## Promise.race() ## Promise.race()
`Promise.race`方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 `Promise.race()`方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
```javascript ```javascript
const p = Promise.race([p1, p2, p3]); const p = Promise.race([p1, p2, p3]);
@ -670,7 +670,7 @@ const p = Promise.race([p1, p2, p3]);
上面代码中,只要`p1``p2``p3`之中有一个实例率先改变状态,`p`的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给`p`的回调函数。 上面代码中,只要`p1``p2``p3`之中有一个实例率先改变状态,`p`的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给`p`的回调函数。
`Promise.race`方法的参数与`Promise.all`方法一样,如果不是 Promise 实例,就会先调用下面讲到的`Promise.resolve`方法,将参数转为 Promise 实例,再进一步处理。 `Promise.race()`方法的参数与`Promise.all()`方法一样,如果不是 Promise 实例,就会先调用下面讲到的`Promise.resolve()`方法,将参数转为 Promise 实例,再进一步处理。
下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为`reject`,否则变为`resolve` 下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为`reject`,否则变为`resolve`
@ -689,9 +689,138 @@ p
上面代码中,如果 5 秒之内`fetch`方法无法返回结果,变量`p`的状态就会变为`rejected`,从而触发`catch`方法指定的回调函数。 上面代码中,如果 5 秒之内`fetch`方法无法返回结果,变量`p`的状态就会变为`rejected`,从而触发`catch`方法指定的回调函数。
## Promise.allSettled()
`Promise.allSettled()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是`fulfilled`还是`rejected`,包装实例才会结束。该方法由 [ES2020](https://github.com/tc39/proposal-promise-allSettled) 引入。
```javascript
const promises = [
fetch('/api-1'),
fetch('/api-2'),
fetch('/api-3'),
];
await Promise.allSettled(promises);
removeLoadingIndicator();
```
上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。
该方法返回的新的 Promise 实例,一旦结束,状态总是`fulfilled`,不会变成`rejected`。状态变成`fulfilled`Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入`Promise.allSettled()`的 Promise 实例。
```javascript
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
```
上面代码中,`Promise.allSettled()`的返回值`allSettledPromise`,状态只可能变成`fulfilled`。它的监听函数接收到的参数是数组`results`。该数组的每个成员都是一个对象,对应传入`Promise.allSettled()`的两个 Promise 实例。每个对象都有`status`属性,该属性的值只可能是字符串`fulfilled`或字符串`rejected``fulfilled`时,对象有`value`属性,`rejected`时有`reason`属性,对应两种状态的返回值。
下面是返回值用法的例子。
```javascript
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === 'fulfilled');
// 过滤出失败的请求,并输出原因
const errors = results
.filter(p => p.status === 'rejected')
.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()
`Promise.any()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成`fulfilled`状态,包装实例就会变成`fulfilled`状态;如果所有参数实例都变成`rejected`状态,包装实例就会变成`rejected`状态。该方法目前是一个第三阶段的[提案](https://github.com/tc39/proposal-promise-any) 。
`Promise.any()``Promise.race()`方法很像,只有一点不同,就是不会因为某个 Promise 变成`rejected`状态而结束。
```javascript
const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
console.log(first);
} catch (error) {
console.log(error);
}
```
上面代码中,`Promise.any()`方法的参数数组包含三个 Promise 操作。其中只要有一个变成`fulfilled``Promise.any()`返回的 Promise 对象就变成`fulfilled`。如果所有三个操作都变成`rejected`,那么就会`await`命令就会抛出错误。
`Promise.any()`抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被`rejected`的操作所抛出的错误。下面是 AggregateError 的实现示例。
```javascript
new AggregateError() extends Array -> AggregateError
const err = new AggregateError();
err.push(new Error("first error"));
err.push(new Error("second error"));
throw err;
```
捕捉错误时,如果不用`try...catch`结构和 await 命令,可以像下面这样写。
```javascript
Promise.any(promises).then(
(first) => {
// Any of the promises was fulfilled.
},
(error) => {
// All of the promises were rejected.
}
);
```
下面是一个例子。
```javascript
var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result); // 42
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results); // [-1, Infinity]
});
```
## Promise.resolve() ## Promise.resolve()
有时需要将现有对象转为 Promise 对象,`Promise.resolve`方法就起到这个作用。 有时需要将现有对象转为 Promise 对象,`Promise.resolve()`方法就起到这个作用。
```javascript ```javascript
const jsPromise = Promise.resolve($.ajax('/whatever.json')); const jsPromise = Promise.resolve($.ajax('/whatever.json'));
@ -699,7 +828,7 @@ const jsPromise = Promise.resolve($.ajax('/whatever.json'));
上面代码将 jQuery 生成的`deferred`对象,转为一个新的 Promise 对象。 上面代码将 jQuery 生成的`deferred`对象,转为一个新的 Promise 对象。
`Promise.resolve`等价于下面的写法。 `Promise.resolve()`等价于下面的写法。
```javascript ```javascript
Promise.resolve('foo') Promise.resolve('foo')

View File

@ -273,12 +273,60 @@ const showSplashScreen = response.settings.showSplashScreen ?? true;
上面代码中,默认值只有在属性值为`null``undefined`时,才会生效。 上面代码中,默认值只有在属性值为`null``undefined`时,才会生效。
这个运算符可以跟链判断运算符`?.`配合使用 这个运算符的一个目的,就是跟链判断运算符`?.`配合使用,为`null``undefined`的值设置默认值
```javascript ```javascript
const animationDuration = response.settings?.animationDuration ?? 300; const animationDuration = response.settings?.animationDuration ?? 300;
``` ```
上面代码中,`response.settings`如果是`null``undefined`就会返回默认值300。
这个运算符很适合判断函数参数是否赋值。
```javascript
function Component(props) {
const enable = props.enabled ?? true;
// …
}
```
上面代码判断`props`参数的`enabled`属性是否赋值,等同于下面的写法。
```javascript
function Component(props) {
const {
enabled: enable = true,
} = props;
// …
}
```
`??`有一个运算优先级问题,它与`&&``||`的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。
```javascript
// 报错
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs
```
上面四个表达式都会报错,必须加入表明优先级的括号。
```javascript
(lhs && middle) ?? rhs;
lhs && (middle ?? rhs);
(lhs ?? middle) && rhs;
lhs ?? (middle && rhs);
(lhs || middle) ?? rhs;
lhs || (middle ?? rhs);
(lhs ?? middle) || rhs;
lhs ?? (middle || rhs);
```
## 函数的部分执行 ## 函数的部分执行
### 语法 ### 语法
@ -646,12 +694,15 @@ BigInt(1.5) // RangeError
BigInt('1.5') // SyntaxError BigInt('1.5') // SyntaxError
``` ```
BigInt 对象继承了 Object 提供的实例方法。 BigInt 对象继承了 Object 对象的两个实例方法。
- `BigInt.prototype.toLocaleString()`
- `BigInt.prototype.toString()` - `BigInt.prototype.toString()`
- `BigInt.prototype.valueOf()` - `BigInt.prototype.valueOf()`
它还继承了 Number 对象的一个实例方法。
- `BigInt.prototype.toLocaleString()`
此外,还提供了三个静态方法。 此外,还提供了三个静态方法。
- `BigInt.asUintN(width, BigInt)` 给定的 BigInt 转为 0 到 2<sup>width</sup> - 1 之间对应的值。 - `BigInt.asUintN(width, BigInt)` 给定的 BigInt 转为 0 到 2<sup>width</sup> - 1 之间对应的值。

View File

@ -132,6 +132,17 @@ codePointLength(s) // 2
上面代码中,不加`u`修饰符,就无法识别非规范的`K`字符。 上面代码中,不加`u`修饰符,就无法识别非规范的`K`字符。
**6转义**
没有`u`修饰符的情况下,正则中没有定义的转义(如逗号的转义`\,`)无效,而在`u`模式会报错。
```javascript
/\,/ // /\,/
/\,/u // 报错
```
上面代码中,没有`u`修饰符时,逗号前面的反斜杠是无效的,加了`u`修饰符就报错。
## RegExp.prototype.unicode 属性 ## RegExp.prototype.unicode 属性
正则实例对象新增`unicode`属性,表示是否设置了`u`修饰符。 正则实例对象新增`unicode`属性,表示是否设置了`u`修饰符。

View File

@ -354,7 +354,7 @@ const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…) // Uncaught TypeError: Invalid value used in weak set(…)
``` ```
上面代码中,数组`b`的成员不是对象,加入 WeaKSet 就会报错。 上面代码中,数组`b`的成员不是对象,加入 WeakSet 就会报错。
WeakSet 结构有以下三个方法。 WeakSet 结构有以下三个方法。

View File

@ -31,11 +31,11 @@ String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
ES6 还为原生的 String 对象,提供了一个`raw()`方法。该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。 ES6 还为原生的 String 对象,提供了一个`raw()`方法。该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。
```javascript ```javascript
String.raw`Hi\n${2+3}!`; String.raw`Hi\n${2+3}!`
// 返回 "Hi\\n5!" // 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"
String.raw`Hi\u000A!`; String.raw`Hi\u000A!`;
// 返回 "Hi\\u000A!" // 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!"
``` ```
如果原字符串的斜杠已经转义,那么`String.raw()`会进行再次转义。 如果原字符串的斜杠已经转义,那么`String.raw()`会进行再次转义。
@ -49,16 +49,16 @@ String.raw`Hi\\n` === "Hi\\\\n" // true
`String.raw()`方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。 `String.raw()`方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。
`String.raw()`方法也可以作为正常的函数使用。这时,它的第一个参数,应该是一个具有`raw`属性的对象,且`raw`属性的值应该是一个数组。 `String.raw()`本质上是一个正常的函数,只是专用于模板字符串的标签函数。如果写成正常函数的形式,它的第一个参数,应该是一个具有`raw`属性的对象,且`raw`属性的值应该是一个数组,对应模板字符串解析后的值
```javascript ```javascript
String.raw({ raw: 'test' }, 0, 1, 2); // `foo${1 + 2}bar`
// 't0e1s2t'
// 等同于 // 等同于
String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2); String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"
``` ```
上面代码中,`String.raw()`方法的第一个参数是一个对象,它的`raw`属性等同于原始的模板字符串解析后得到的数组。
作为函数,`String.raw()`的代码实现基本如下。 作为函数,`String.raw()`的代码实现基本如下。
```javascript ```javascript