Skip to content

Commit 40fbdf3

Browse files
committed
UPD: update to 2020-10-15
1 parent 26dbf2e commit 40fbdf3

File tree

18 files changed

+237
-136
lines changed

18 files changed

+237
-136
lines changed

1-js/04-object-basics/07-optional-chaining/article.md

+29-21
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@
33

44
[recent browser="new"]
55

6-
可选链 `?.` 是一种访问嵌套对象属性的防错误方法。即使中间的属性不存在,也不会出现错误。
6+
可选链 `?.` 是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误。
77

8-
## 问题
8+
## “不存在的属性”的问题
99

1010
如果你才刚开始读此教程并学习 JavaScript,那可能还没接触到这个问题,但它却相当常见。
1111

12-
例如,我们有些用户会有地址信息,但有一少部分用户并没有提供相关信息。那么我们就不能安全地读取到 `user.address.street`
12+
例如,让我们考虑存储用户数据的对象。我们大多数用户的地址信息都存储在 `user.address` 属性中,街道信息存储在 `user.address.street` 属性中,但有些用户没有提供这些信息。
13+
14+
在这种情况下,当我们尝试获取 `user.address.street` 时,将会收到错误信息:
1315

1416
```js run
15-
let user = {}; // 这个 user 恰巧没有 address
17+
let user = {}; // 变量 user 没有 "address" 属性
1618

1719
alert(user.address.street); // Error!
1820
```
1921

20-
或者,在 Web 开发中,我们想获取页面上某个元素的信息,但它可能不存在:
22+
这是预期的结果,JavaScript 的工作原理就是这样的,但是在很多实际场景中,我们更希望得到的是 `undefined`(表示没有 `street` 属性)而不是一个错误。
23+
24+
……还有另一个例子。在 Web 开发中,我们可能需要获取页面上某个元素的有关信息,但有时该信息可能不存在:
2125

2226
```js run
2327
// 如果 querySelector(...) 的结果为 null,则会报错
@@ -34,7 +38,7 @@ let user = {}; // user 没有 address
3438
alert( user && user.address && user.address.street ); // undefined(不报错)
3539
```
3640

37-
依次对整条路径上的属性使用与运算进行判断,以确保所有节点是存在的,但是写起来很麻烦。
41+
依次对整条路径上的属性使用与运算进行判断,以确保所有节点是存在的(如果不存在,则停止计算),但是写起来很麻烦。
3842

3943
## 可选链
4044

@@ -70,7 +74,7 @@ alert( user?.address.street ); // undefined
7074

7175
例如,如果根据我们的代码逻辑,`user` 对象必须存在,但 `address` 是可选的,那么 `user.address?.street` 会更好。
7276

73-
所以,如果 `user` 恰巧因为失误变为 undefined我们会知道并修复这个失误。否则,代码中的 error 在不恰当的地方被消除了,这会导致调试更加困难。
77+
所以,如果 `user` 恰巧因为失误变为 undefined我们会看到一个编程错误并修复它。否则,代码中的错误在不恰当的地方被消除了,这会导致调试更加困难。
7478
```
7579
7680
````warn header="`?.` 前的变量必须已声明"
@@ -80,25 +84,27 @@ alert( user?.address.street ); // undefined
8084
// ReferenceError: user is not defined
8185
user?.address;
8286
```
83-
`?.` 前的变量必须已通过 `let/const/var user` 进行声明。可选链仅适用于已声明的变量。
87+
`?.` 前的变量必须已声明(例如 `let/const/var user`。可选链仅适用于已声明的变量。
8488
````
8589

8690
## 短路效应
8791

8892
正如前面所说的,如果 `?.` 左边部分不存在,就会立即停止运算(“短路效应”)。
8993

90-
所以,如果后面有任何函数调用或者副作用,它们均不会执行:
94+
所以,如果后面有任何函数调用或者副作用,它们均不会执行。
95+
96+
例如:
9197

9298
```js run
9399
let user = null;
94100
let x = 0;
95101
96-
user?.sayHi(x++); // 没事发生
102+
user?.sayHi(x++); // 没有 "sayHi",因此代码执行没有触达 x++
97103
98104
alert(x); // 0,值没有增加
99105
```
100106

101-
## 其它情况?.(),?.[]
107+
## 其它变体?.(),?.[]
102108

103109
可选链 `?.` 不是一个运算符,而是一个特殊的语法结构。它还可以与函数和方括号一起使用。
104110

@@ -121,9 +127,9 @@ user2.admin?.();
121127
*/!*
122128
```
123129

124-
在这两行代码中,我们首先使用点符号 `.` 来获取 `admin` 属性,因为用户对象一定存在,因此可以安全地读取它。
130+
在这两行代码中,我们首先使用点符号`user1.admin`来获取 `admin` 属性,因为用户对象一定存在,因此可以安全地读取它。
125131

126-
然后 `?.()` 会检查它左边的部分:如果 admin 函数存在,那么就调用运行它(对于 `user1`)。否则(对于 `user2`)运算停止,没有错误。
132+
然后 `?.()` 会检查它左边的部分:如果 `admin` 函数存在,那么就调用运行它(对于 `user1`)。否则(对于 `user2`)运算停止,没有错误。
127133

128134
如果我们想使用方括号 `[]` 而不是点符号 `.` 来访问属性,语法 `?.[]` 也可以使用。跟前面的例子类似,它允许从一个可能不存在的对象上安全地读取属性。
129135

@@ -148,28 +154,30 @@ alert( user1?.[key]?.something?.not?.existing); // undefined
148154
delete user?.name; // 如果 user 存在,则删除 user.name
149155
```
150156

151-
```warn header="我们可以使用 `?.` 来安全地读取或删除,但不能写入"
152-
可选链 `?.` 不能用在赋值语句的左侧
157+
````warn header="我们可以使用 `?.` 来安全地读取或删除,但不能写入"
158+
可选链 `?.` 不能用在赋值语句的左侧
153159

160+
例如:
154161
```js run
155-
// 下面这段代码的想法是要写入 user.name,如果 user 存在的话
162+
let user = null;
156163
157164
user?.name = "John"; // Error,不起作用
158-
// because it evaluates to undefined = "John"
165+
// 因为它在计算的是 undefined = "John"
159166
```
160167

168+
这不是那么聪明。
169+
````
170+
161171
## 总结
162172

163173
可选链 `?.` 语法有三种形式:
164174

165175
1. `obj?.prop` —— 如果 `obj` 存在则返回 `obj.prop`,否则返回 `undefined`
166176
2. `obj?.[prop]` —— 如果 `obj` 存在则返回 `obj[prop]`,否则返回 `undefined`
167-
3. `obj?.method()` —— 如果 `obj` 存在则调用 `obj.method()`,否则返回 `undefined`
177+
3. `obj.method?.()` —— 如果 `obj.method` 存在则调用 `obj.method()`,否则返回 `undefined`
168178

169179
正如我们所看到的,这些语法形式用起来都很简单直接。`?.` 检查左边部分是否为 `null/undefined`,如果不是则继续运算。
170180

171181
`?.` 链使我们能够安全地访问嵌套属性。
172182

173-
但是,我们应该谨慎地使用 `?.`,仅在当左边部分不存在也没问题的情况下使用为宜。
174-
175-
以保证在代码中有编程上的 error 出现时,也不会对我们隐藏。
183+
但是,我们应该谨慎地使用 `?.`,仅在当左边部分不存在也没问题的情况下使用为宜。以保证在代码中有编程上的错误出现时,也不会对我们隐藏。

1-js/04-object-basics/08-symbol/article.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ user.id = "Their id value"
121121
// 砰!无意中被另一个脚本重写了 id!
122122
```
123123

124-
### 字面量中的 Symbol
124+
### 对象字面量中的 Symbol
125125

126126
如果我们要在对象字面量 `{...}` 中使用 Symbol,则需要使用方括号把它括起来。
127127

1-js/05-data-types/03-string/article.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ if (str.indexOf("Widget") != -1) {
312312

313313
#### 按位(bitwise)NOT 技巧
314314

315-
这里使用的一个老技巧是 [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` 运算符。它将数字转换为 32-bit 整数(如果存在小数部分,则删除小数部分),然后对其二进制表示形式中的所有位均取反。
315+
这里使用的一个老技巧是 [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT) `~` 运算符。它将数字转换为 32-bit 整数(如果存在小数部分,则删除小数部分),然后对其二进制表示形式中的所有位均取反。
316316

317317
实际上,这意味着一件很简单的事儿:对于 32-bit 整数,`~n` 等于 `-(n+1)`
318318

1-js/05-data-types/04-array/article.md

+53-2
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ alert( fruits );
193193

194194
它们扩展了对象,提供了特殊的方法来处理有序的数据集合以及 `length` 属性。但从本质上讲,它仍然是一个对象。
195195

196-
记住,在 JavaScript 中只有 7 种基本类型。数组是一个对象,因此其行为也像一个对象。
196+
记住,在 JavaScript 中只有 8 种基本的数据类型(详见 [数据类型](info:types) 一章)。数组是一个对象,因此其行为也像一个对象。
197197

198198
例如,它是通过引用来复制的:
199199

@@ -429,6 +429,53 @@ alert( "1" + 1 ); // "11"
429429
alert( "1,2" + 1 ); // "1,21"
430430
```
431431

432+
## 不要使用 == 比较数组
433+
434+
JavaScript 中的数组与其它一些编程语言的不同,不应该使用 `==` 运算符比较 JavaScript 中的数组。
435+
436+
该运算符不会对数组进行特殊处理,它会像处理任意对象那样处理数组。
437+
438+
让我们回顾一下规则:
439+
440+
- 仅当两个对象引用的是同一个对象时,它们才相等 `==`
441+
- 如果 `==` 左右两个参数之中有一个参数是对象,另一个参数是原始类型,那么该对象将会被转换为原始类型,转换规则如 <info:object-toprimitive> 一章所述。
442+
- ……`null``undefined` 相等 `==`,且各自不等于任何其他的值。
443+
444+
严格比较 `===` 更简单,因为它不会进行类型转换。
445+
446+
所以,如果我们使用 `==` 来比较数组,除非我们比较的是两个引用同一数组的变量,否则它们永远不相等。
447+
448+
例如:
449+
```js run
450+
alert( [] == [] ); // false
451+
alert( [0] == [0] ); // false
452+
```
453+
454+
从技术上讲,这些数组是不同的对象。所以它们不相等。`==` 运算符不会进行逐项比较。
455+
456+
与原始类型的比较也可能会产生看似很奇怪的结果:
457+
458+
```js run
459+
alert( 0 == [] ); // true
460+
461+
alert('0' == [] ); // false
462+
```
463+
464+
在这里的两个例子中,我们将原始类型和数组对象进行比较。因此,数组 `[]` 被转换为原始类型以进行比较,被转换成了一个空字符串 `''`
465+
466+
然后,接下来的比较就是原始类型之间的比较,如 <info:type-conversions> 一章所述:
467+
468+
```js run
469+
// 在 [] 被转换为 '' 后
470+
alert( 0 == '' ); // true,因为 '' 被转换成了数字 0
471+
472+
alert('0' == '' ); // false,没有进一步的类型转换,是不同的字符串
473+
```
474+
475+
那么,我们应该如何对数组进行比较呢?
476+
477+
很简单,不要使用 `==` 运算符。而是,可以在循环中或者使用下一章中我们将介绍的迭代方法逐项地比较它们。
478+
432479
## 总结
433480

434481
数组是一种特殊的对象,适用于存储和管理有序的数据项。
@@ -460,4 +507,8 @@ alert( "1,2" + 1 ); // "1,21"
460507
- `for (let item of arr)` — 现代语法,只能访问 items。
461508
- `for (let i in arr)` — 永远不要用这个。
462509

463-
在下一章节 <info:array-methods> 中,我们会继续学习数组,学习更多添加、移除、提取元素和数组排序的方法。
510+
比较数组时,不要使用 `==` 运算符(当然也不要使用 `>``<` 等运算符),因为它们不会对数组进行特殊处理。它们通常会像处理任意对象那样处理数组,这通常不是我们想要的。
511+
512+
但是,我们可以使用 `for..of` 循环来逐项比较数组。
513+
514+
在下一章 <info:array-methods> 中,我们将继续学习数组,学习更多添加、移除、提取元素和数组排序的方法。

1-js/05-data-types/05-array-methods/2-filter-range/task.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ importance: 4
44

55
# 过滤范围
66

7-
写一个函数 `filterRange(arr, a, b)`,该函数获取一个数组 `arr`在其中查找数值大小在 `a``b` 之间的元素,并返回它们的数组
7+
写一个函数 `filterRange(arr, a, b)`,该函数获取一个数组 `arr`在其中查找数值大于或等于 `a`,且小于或等于 `b` 的元素,并将结果以数组的形式返回
88

99
该函数不应该修改原数组。它应该返回新的数组。
1010

1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ function Calculator() {
1010
let split = str.split(' '),
1111
a = +split[0],
1212
op = split[1],
13-
b = +split[2]
13+
b = +split[2];
1414

1515
if (!this.methods[op] || isNaN(a) || isNaN(b)) {
1616
return NaN;
1717
}
1818

1919
return this.methods[op](a, b);
20-
}
20+
};
2121

2222
this.addMethod = function(name, func) {
2323
this.methods[name] = func;

1-js/05-data-types/05-array-methods/8-sort-objects/solution.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
```js run no-beautify
22
function sortByAge(arr) {
3-
arr.sort((a, b) => a.age > b.age ? 1 : -1);
3+
arr.sort((a, b) => a.age - b.age);
44
}
55

66
let john = { name: "John", age: 25 };

1-js/05-data-types/05-array-methods/article.md

+21-7
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ alert( arr.length ); // 3
3636

3737
所以应该使用特殊的方法。
3838

39-
[arr.splice(str)](mdn:js/Array/splice) 方法可以说是处理数组的瑞士军刀。它可以做所有事情:添加,删除和插入元素。
39+
[arr.splice](mdn:js/Array/splice) 方法可以说是处理数组的瑞士军刀。它可以做所有事情:添加,删除和插入元素。
4040

4141
语法是:
4242

4343
```js
44-
arr.splice(index[, deleteCount, elem1, ..., elemN])
44+
arr.splice(start[, deleteCount, elem1, ..., elemN])
4545
```
4646

47-
`index` 开始:删除 `deleteCount` 个元素并在当前位置插入 `elem1, ..., elemN`最后返回已删除元素的数组
47+
它从索引 `start` 开始修改 `arr`:删除 `deleteCount` 个元素并在当前位置插入 `elem1, ..., elemN`最后返回已被删除元素的数组
4848

4949
通过例子我们可以很容易地掌握这个方法。
5050

@@ -419,13 +419,14 @@ alert(arr); // *!*1, 2, 15*/!*
419419

420420
我们思考一下这儿发生了什么。`arr` 可以是由任何内容组成的数组,对吗?它可能包含数字、字符串、对象或其他任何内容。我们有一组 **一些元素**。要对其进行排序,我们需要一个 **排序函数** 来确认如何比较这些元素。默认是按字符串进行排序的。
421421

422-
`arr.sort(fn)` 方法实现了通用的排序算法。我们不需要关心它的内部工作原理(大多数情况下都是经过 [快速排序](https://en.wikipedia.org/wiki/Quicksort) 算法优化的)。它将遍历数组,使用提供的函数比较其元素并对其重新排序,我们所需要的就是提供执行比较的函数 `fn`
422+
`arr.sort(fn)` 方法实现了通用的排序算法。我们不需要关心它的内部工作原理(大多数情况下都是经过 [快速排序](https://en.wikipedia.org/wiki/Quicksort) [Timsort](https://en.wikipedia.org/wiki/Timsort) 算法优化的)。它将遍历数组,使用提供的函数比较其元素并对其重新排序,我们所需要的就是提供执行比较的函数 `fn`
423423

424424
顺便说一句,如果我们想知道要比较哪些元素 —— 那么什么都不会阻止 alert 它们:
425425

426426
```js run
427427
[1, -2, 15, 2, 0, 8].sort(function(a, b) {
428428
alert( a + " <> " + b );
429+
return a - b;
429430
});
430431
```
431432

@@ -712,11 +713,11 @@ alert(soldiers[1].age); // 23
712713
- `shift()` —— 从首端提取一个元素,
713714
- `unshift(...items)` —— 向首端添加元素,
714715
- `splice(pos, deleteCount, ...items)` —— 从 `pos` 开始删除 `deleteCount` 个元素,并插入 `items`
715-
- `slice(start, end)` —— 创建一个新数组,将从位置 `start` 到位置 `end`(但不包括 `end`)的元素复制进去。
716+
- `slice(start, end)` —— 创建一个新数组,将从索引 `start` 到索引 `end`(但不包括 `end`)的元素复制进去。
716717
- `concat(...items)` —— 返回一个新数组:复制当前数组的所有元素,并向其中添加 `items`。如果 `items` 中的任意一项是一个数组,那么就取其元素。
717718

718719
- 搜索元素:
719-
- `indexOf/lastIndexOf(item, pos)` —— 从位置 `pos` 开始搜索 `item`,搜索到则返回该项的索引,否则返回 `-1`
720+
- `indexOf/lastIndexOf(item, pos)` —— 从索引 `pos` 开始搜索 `item`,搜索到则返回该项的索引,否则返回 `-1`
720721
- `includes(value)` —— 如果数组有 `value`,则返回 `true`,否则返回 `false`
721722
- `find/filter(func)` —— 通过 `func` 过滤元素,返回使 `func` 返回 `true` 的第一个值/所有值。
722723
- `findIndex``find` 类似,但返回索引而不是值。
@@ -729,7 +730,7 @@ alert(soldiers[1].age); // 23
729730
- `sort(func)` —— 对数组进行原位(in-place)排序,然后返回它。
730731
- `reverse()` —— 原位(in-place)反转数组,然后返回它。
731732
- `split/join` —— 将字符串转换为数组并返回。
732-
- `reduce(func, initial)` —— 通过对每个元素调用 `func` 计算数组上的单个值,并在调用之间传递中间结果。
733+
- `reduce/reduceRight(func, initial)` —— 通过对每个元素调用 `func` 计算数组上的单个值,并在调用之间传递中间结果。
733734

734735
- 其他:
735736
- `Array.isArray(arr)` 检查 `arr` 是否是一个数组。
@@ -742,10 +743,23 @@ alert(soldiers[1].age); // 23
742743

743744
`map` 类似,对数组的每个元素调用函数 `fn`。如果任何/所有结果为 `true`,则返回 `true`,否则返回 `false`
744745

746+
这两个方法的行为类似于 `||``&&` 运算符:如果 `fn` 返回一个真值,`arr.some()` 立即返回 `true` 并停止迭代其余数组项;如果 `fn` 返回一个假值,`arr.every()` 立即返回 `false` 并停止对其余数组项的迭代。
747+
748+
我们可以使用 `every` 来比较数组:
749+
```js run
750+
function arraysEqual(arr1, arr2) {
751+
return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]);
752+
}
753+
754+
alert( arraysEqual([1, 2], [1, 2])); // true
755+
```
756+
745757
- [arr.fill(value, start, end)](mdn:js/Array/fill) —— 从索引 `start``end`,用重复的 `value` 填充数组。
746758

747759
- [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) —— 将从位置 `start``end` 的所有元素复制到 **自身**`target` 位置(覆盖现有元素)。
748760

761+
- [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap) 从多维数组创建一个新的扁平数组。
762+
749763
有关完整列表,请参阅 [手册](mdn:js/Array)
750764

751765
乍看起来,似乎有很多方法,很难记住。但实际上这比看起来要容易得多。

1-js/05-data-types/06-iterable/article.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ alert( str.slice(1, 3) ); // 乱码(两个不同 UTF-16 扩展字符碎片拼
293293
可以应用 `for..of` 的对象被称为 **可迭代的**
294294

295295
- 技术上来说,可迭代对象必须实现 `Symbol.iterator` 方法。
296-
- `obj[Symbol.iterator]` 的结果被称为 **迭代器(iterator)**。由它处理进一步的迭代过程。
296+
- `obj[Symbol.iterator]()` 的结果被称为 **迭代器(iterator)**。由它处理进一步的迭代过程。
297297
- 一个迭代器必须有 `next()` 方法,它返回一个 `{done: Boolean, value: any}` 对象,这里 `done:true` 表明迭代结束,否则 `value` 就是下一个值。
298298
- `Symbol.iterator` 方法会被 `for..of` 自动调用,但我们也可以直接调用它。
299299
- 内置的可迭代对象例如字符串和数组,都实现了 `Symbol.iterator`

1-js/05-data-types/11-date/1-new-date/solution.md

+12-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22

33
所以二月对应的数值是 1。
44

5+
这是一个以数字作为日期参数的示例:
6+
7+
```js run
8+
// new Date(year, month, date, hour, minute, second, millisecond)
9+
let d1 = new Date(2012, 1, 20, 3, 12);
10+
alert( d1 );
11+
```
12+
我们还可以从字符串创建日期,像这样:
13+
514
```js run
6-
let d = new Date(2012, 1, 20, 3, 12);
7-
alert( d );
15+
// new Date(datastring)
16+
let d2 = new Date("February 20, 2012 03:12:00");
17+
alert( d2 );
818
```

1-js/05-data-types/11-date/article.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
new Date(2011, 0, 1); // 同样,时分秒等均为默认值 0
7070
```
7171

72-
时间度量最小精确到 1 毫秒(1/1000 秒):
72+
时间度量最大精确到 1 毫秒(1/1000 秒):
7373

7474
```js run
7575
let date = new Date(2011, 0, 1, 2, 3, 4, 567);

1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg

+1
Loading

1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg

+1
Loading

1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg

+1
Loading

1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg

-1
This file was deleted.

0 commit comments

Comments
 (0)