Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 1-js/01-getting-started/1-intro/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ JavaScript 的能力很大程度上取决于它运行的环境。例如,[Node.
现代浏览器允许 JavaScript 做一些文件相关的操作,但是这个操作是受到限制的。仅当用户做出特定的行为,JavaScript 才能操作这个文件。例如,用户把文件“拖放”到浏览器中,或者通过 `<input>` 标签选择了文件。

有很多与相机/麦克风和其它设备进行交互的方式,但是这些都需要获得用户的明确许可。因此,启用了 JavaScript 的网页应该不会偷偷地启动网络摄像头观察你,并把你的信息发送到 [美国国家安全局](https://en.wikipedia.org/wiki/National_Security_Agency)。
- 不同的标签页/窗口之间通常互不了解。有时候,也会有一些联系。例如,一个标签页通过 JavaScript 打开另外一个标签页。但即使在这种情况下,如果两个标签页打开的不是同一个网站(域名、协议或者端口任一不相同的网站),它们都不能相互通信。
- 不同的标签页/窗口之间通常互不了解。有时候,也会有一些联系,例如一个标签页通过 JavaScript 打开的另外一个标签页。但即使在这种情况下,如果两个标签页打开的不是同一个网站(域名、协议或者端口任一不相同的网站),它们都不能相互通信。

这就是所谓的“同源策略”。为了解决“同源策略”问题,两个标签页必须 **都** 包含一些处理这个问题的特定的 JavaScript 代码,并均允许数据交换。本教程会讲到这部分相关的知识。

Expand Down
2 changes: 1 addition & 1 deletion 1-js/03-code-quality/02-coding-style/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ function pow(x, n) {
},
"rules": {
"no-console": 0,
"indent": ["warning", 2]
"indent": 2
}
}
```
Expand Down
4 changes: 2 additions & 2 deletions 1-js/03-code-quality/06-polyfills/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ JavaScript 引擎背后的团队关于首先要实现什么有着他们自己想

2. 第二,polyfill。

新的语言特性可能包括新的内建函数和语法结构
transpiler 会重写代码,将语法结构转换为旧的结构。但是对于新的内建函数,需要我们去实现。JavaScript 是一个高度动态化的语言。脚本可以添加/修改任何函数,从而使它们的行为符合现代标准。
新的语言特性可能不仅包括语法结构,还包括新的内建函数
Transpiler 会重写代码,将语法结构转换为旧的结构。但是对于新的内建函数,需要我们去实现。JavaScript 是一个高度动态化的语言。脚本可以添加/修改任何函数,从而使它们的行为符合现代标准。

更新/添加新函数的脚本称为 “polyfill”。它“填补”了缺口,并添加了缺少的实现。

Expand Down
2 changes: 1 addition & 1 deletion 1-js/04-object-basics/03-garbage-collection/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ JavaScript 中主要的内存管理概念是 **可达性**。

2. 如果一个值可以通过引用或引用链从根访问任何其他值,则认为该值是可达的。

比方说,如果全局变量中有一个对象,并且该对象有一个属性引用了另一个对象,则该对象被认为是可达的。而且它引用的内容也是可达的。下面是详细的例子。
比方说,如果全局变量中有一个对象,并且该对象有一个属性引用了另一个对象,则 **该** 对象被认为是可达的。而且它引用的内容也是可达的。下面是详细的例子。

在 JavaScript 引擎中有一个被称作 [垃圾回收器](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) 的东西在后台执行。它监控着所有对象的状态,并删除掉那些已经不可达的。

Expand Down
95 changes: 67 additions & 28 deletions 1-js/04-object-basics/07-optional-chaining/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,52 +9,90 @@

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

例如,让我们考虑存储用户数据的对象。我们大多数用户的地址信息都存储在 `user.address` 属性中,街道信息存储在 `user.address.street` 属性中,但有些用户没有提供这些信息
举个例子,假设我们有很多个 `user` 对象,其中存储了我们的用户数据

在这种情况下,当我们尝试获取 `user.address.street` 时,将会收到错误信息:
我们大多数用户的地址都存储在 `user.address` 中,街道地址存储在 `user.address.street` 中,但有些用户没有提供这些信息。

在这种情况下,当我们尝试获取 `user.address.street`,而该用户恰好没提供地址信息,我们则会收到一个错误:

```js run
let user = {}; // 变量 user 没有 "address" 属性
let user = {}; // 一个没有 "address" 属性的 user 对象

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

这是预期的结果,JavaScript 的工作原理就是这样的,但是在很多实际场景中,我们更希望得到的是 `undefined`(表示没有 `street` 属性)而不是一个错误。
这是预期的结果。JavaScript 的工作原理就是这样的。因为 `user.address` 为 `undefined`,尝试读取 `user.address.street` 会失败,并收到一个错误。

但是在很多实际场景中,我们更希望得到的是 `undefined`(表示没有 `street` 属性)而不是一个错误。

……还有另一个例子。在 Web 开发中,我们可能需要获取页面上某个元素的有关信息,但有时该信息可能不存在:
……还有另一个例子。在 Web 开发中,我们可以使用特殊的方法调用(例如 `document.querySelector('.elem')`)以对象的形式获取一个网页元素,如果没有这种对象,则返回 `null`。

```js run
// 如果 querySelector(...) 的结果为 null,则会报错
let html = document.querySelector('.my-element').innerHTML;
// 如果 document.querySelector('.elem') 的结果为 null,则这里不存在这个元素
let html = document.querySelector('.elem').innerHTML; // 如果 document.querySelector('.elem') 的结果为 null,则会出现错误
```

在 JavaScript 这门语言中出现 `?.` 前,`&&` 运算符常被用来解决这个问题
同样,如果该元素不存在,则访问 `null` 的 `.innerHTML` 时会出错。在某些情况下,当元素的缺失是没问题的时候,我们希望避免出现这种错误,而是接受 `html = null` 作为结果

例如:
我们如何实现这一点呢?

可能最先想到的方案是在访问该值的属性之前,使用 `if` 或条件运算符 `?` 对该值进行检查,像这样:

```js
let user = {};

alert(user.address ? user.address.street : undefined);
```

这样可以,这里就不会出现错误了……但是不够优雅。就像你所看到的,`"user.address"` 在代码中出现了两次。对于嵌套层次更深的属性就会出现更多次这样的重复,这就是问题了。

例如,让我们尝试获取 `user.address.street.name`。

我们既需要检查 `user.address`,又需要检查 `user.address.street`:

```js
let user = {}; // user 没有 address 属性

alert(user.address ? user.address.street ? user.address.street.name : null : null);
```

这样就太扯淡了,并且这可能导致写出来的代码很难让别人理解。

甚至我们可以先忽略这个问题,因为我们有一种更好的实现方式,就是使用 `&&` 运算符:

```js run
let user = {}; // user 没有 address
let user = {}; // user 没有 address 属性

alert( user && user.address && user.address.street ); // undefined(不报错)
alert( user.address && user.address.street && user.address.street.name ); // undefined(不报错)
```

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

就像你所看到的,在代码中我们仍然重复写了好几遍对象属性名。例如在上面的代码中,`user.address` 被重复写了三遍。

这就是为什么可选链 `?.` 被加入到了 JavaScript 这门编程语言中。那就是彻底地解决以上所有问题!

## 可选链

如果可选链 `?.` 前面部分是 `undefined` 或者 `null`,它会停止运算并返回 `undefined`
如果可选链 `?.` 前面的部分是 `undefined` 或者 `null`,它会停止运算并返回该部分

**为了简明起见,在本文接下来的内容中,我们会说如果一个属性既不是 `null` 也不是 `undefined`,那么它就“存在”。**

下面这是一种安全地访问 `user.address.street` 的方式:
换句话说,例如 `value?.prop`:
- 如果 `value` 存在,则结果与 `value.prop` 相同,
- 否则(当 `value` 为 `undefined/null` 时)则返回 `undefined`。

下面这是一种使用 `?.` 安全地访问 `user.address.street` 的方式:

```js run
let user = {}; // user 没有 address
let user = {}; // user 没有 address 属性

alert( user?.address?.street ); // undefined (不报错)
alert( user?.address?.street ); // undefined(不报错)
```

以 `user?.address` 的方式来读取 `address` 是可行的,即使对象 `user` 不存在:
代码简洁明了,也不用重复写好几遍属性名。

即使 对象 `user` 不存在,使用 `user?.address` 来读取地址也没问题:

```js run
let user = null;
Expand All @@ -65,14 +103,12 @@ alert( user?.address.street ); // undefined

请注意:`?.` 语法使其前面的值成为可选值,但不会对其后面的起作用。

在上面的例子中,`user?.` 只允许 `user` 为 `null/undefined`。

另一方面,如果 `user` 存在,那么它必须具有 `user.address` 属性,否则 `user?.address.street` 在第二个点符号处会报错。
例如,在 `user?.address.street.name` 中,`?.` 允许 `user` 为 `null/undefined`,但仅此而已。更深层次的属性是通过常规方式访问的。如果我们希望它们中的一些也是可选的,那么我们需要使用更多的 `?.` 来替换 `.`。

```warn header="不要过度使用可选链"
我们应该只将 `?.` 使用在一些东西可以不存在的地方。

例如,如果根据我们的代码逻辑,`user` 对象必须存在,但 `address` 是可选的,那么 `user.address?.street` 会更好
例如,如果根据我们的代码逻辑,`user` 对象必须存在,但 `address` 是可选的,那么我们应该这样写 `user.address?.street`,而不是这样 `user?.address?.street`

所以,如果 `user` 恰巧因为失误变为 undefined,我们会看到一个编程错误并修复它。否则,代码中的错误在不恰当的地方被消除了,这会导致调试更加困难。
```
Expand All @@ -84,7 +120,7 @@ alert( user?.address.street ); // undefined
// ReferenceError: user is not defined
user?.address;
```
`?.` 前的变量必须已声明(例如 `let/const/var user`)。可选链仅适用于已声明的变量。
`?.` 前的变量必须已声明(例如 `let/const/var user` 或作为一个函数参数)。可选链仅适用于已声明的变量。
````

## 短路效应
Expand Down Expand Up @@ -113,17 +149,20 @@ alert(x); // 0,值没有增加
在下面这段代码中,有些用户具有 `admin` 方法,而有些没有:

```js run
let user1 = {
let userAdmin = {
admin() {
alert("I am admin");
}
}
};

let user2 = {};
let userGuest = {};

*!*
userAdmin.admin?.(); // I am admin
*/!*

*!*
user1.admin?.(); // I am admin
user2.admin?.();
userGuest.admin?.(); // 啥都没有(没有这样的方法)
*/!*
```

Expand Down Expand Up @@ -165,7 +204,7 @@ user?.name = "John"; // Error,不起作用
// 因为它在计算的是 undefined = "John"
```

这不是那么聪明
这还不是那么智能
````

## 总结
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ importance: 5

---

# 我能添加一个字符串属性吗
# 我能为字符串添加一个属性吗


思考下面的代码:
Expand Down
2 changes: 1 addition & 1 deletion 1-js/05-data-types/02-number/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ JavaScript 有一个内建的 [Math](https://developer.mozilla.org/en/docs/Web/J
alert( Math.pow(2, 10) ); // 2 的 10 次幂 = 1024
```

`Math` 对象中还有更多函数和常量,包括三角函数,你可以在 [Math 函数文档](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) 中找到这些内容。
`Math` 对象中还有更多函数和常量,包括三角函数,你可以在 [Math 对象文档](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) 中找到这些内容。

## 总结

Expand Down
6 changes: 3 additions & 3 deletions 1-js/05-data-types/08-weakmap-weakset/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ let array = [ john ];
john = null; // 覆盖引用

*!*
// john 被存储在数组里, 所以它不会被垃圾回收机制回收
// 我们可以通过 array[0] 来获取它
// 前面由 john 所引用的那个对象被存储在了 array 中
// 所以它不会被垃圾回收机制回收
*/!*
```

Expand All @@ -48,7 +48,7 @@ map.set(john, "...");
john = null; // 覆盖引用

*!*
// john 被存储在 map 中,
// john 被存储在了 map 中,
// 我们可以使用 map.keys() 来获取它
*/!*
```
Expand Down
2 changes: 1 addition & 1 deletion 1-js/06-advanced-functions/01-recursion/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ alert( pow(2, 3) );
当我们完成子调用后 —— 很容易恢复上一个上下文,因为它既保留了变量,也保留了当时所在代码的确切位置。

```smart
在上面的图中,我们使用“行”一词作为示例,每一行只有一个子调用,但通常一行代码可能会包含多个子调用, `pow(…) + pow(…) + somethingElse(…)`。
在上面的图中,我们使用“行(line)”一词,因为在我们的示例中,每一行只有一个子调用,但通常一行代码可能会包含多个子调用,例如 `pow(…) + pow(…) + somethingElse(…)`。

因此,更准确地说,执行是“在子调用之后立即恢复”的。
```
Expand Down
2 changes: 1 addition & 1 deletion 1-js/06-advanced-functions/04-var/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function sayHi() {
}

sayHi();
alert(phrase); // Error: phrase is not defined(检查开发者控制台)
alert(phrase); // Error: phrase is not defined
```

可以看到,`var` 穿透了 `if`,`for` 和其它代码块。这是因为在早期的 JavaScript 中,块没有词法环境,而 `var` 就是这个时期的代表之一。
Expand Down
2 changes: 2 additions & 0 deletions 1-js/06-advanced-functions/05-global-object/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ var gVar = 5;
alert(window.gVar); // 5(成为了全局对象的属性)
```

具有与函数声明相同的效果(在主代码流中具有 `function` 关键字的语句,而不是函数表达式)。

请不要依赖它!这种行为是出于兼容性而存在的。现代脚本通过使用 [JavaScript modules](info:modules) 来避免这种情况的发生。

如果我们使用 `let`,就不会发生这种情况:
Expand Down
2 changes: 1 addition & 1 deletion 1-js/06-advanced-functions/06-function-object/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,6 @@ welcome(); // Hello, Guest(嵌套调用有效)

此外,函数可以带有额外的属性。很多知名的 JavaScript 库都充分利用了这个功能。

它们创建一个“主”函数,然后给它附加很多其它“辅助”函数。例如,[jQuery](https://jquery.com) 库创建了一个名为 `$` 的函数。[lodash](https://lodash.com) 库创建一个 `_` 函数,然后为其添加了 `_.add`、`_.keyBy` 以及其它属性(欲了解详情,参见 [docs](https://lodash.com/docs))。实际上,它们这么做是为了减少对全局空间的污染,这样一个库就只会有一个全局变量。这样就降低了命名冲突的可能性。
它们创建一个“主”函数,然后给它附加很多其它“辅助”函数。例如,[jQuery](https://jquery.com) 库创建了一个名为 `$` 的函数。[lodash](https://lodash.com) 库创建一个 `_` 函数,然后为其添加了 `_.add`、`_.keyBy` 以及其它属性(想要了解更多内容,参查阅 [docs](https://lodash.com/docs))。实际上,它们这么做是为了减少对全局空间的污染,这样一个库就只会有一个全局变量。这样就降低了命名冲突的可能性。

所以,一个函数本身可以完成一项有用的工作,还可以在自身的属性中附带许多其他功能。
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ setTimeout(function run() {

这个限制来自“远古时代”,并且许多脚本都依赖于此,所以这个机制也就存在至今。

对于服务端的 JavaScript,就没有这个限制,并且还有其他调度即时异步任务的方式。例如 Node.js 的 [setImmediate](https://nodejs.org/api/timers.html)。因此,这个提醒只是针对浏览器环境的。
对于服务端的 JavaScript,就没有这个限制,并且还有其他调度即时异步任务的方式。例如 Node.js 的 [setImmediate](https://nodejs.org/api/timers.html#timers_setimmediate_callback_args)。因此,这个提醒只是针对浏览器环境的。
````

## 总结
Expand Down
29 changes: 18 additions & 11 deletions 1-js/08-prototypes/01-prototype-inheritance/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

![prototype](object-prototype-empty.svg)

原型有点“神奇”。当我们想要从 `object` 中读取一个缺失的属性时,JavaScript 会自动从原型中获取该属性。在编程中,这种行为被称为“原型继承”。许多炫酷的语言特性和编程技巧都基于此
当我们从 `object` 中读取一个缺失的属性时,JavaScript 会自动从原型中获取该属性。在编程中,这种行为被称为“原型继承”。很快,我们将通过很多示例来学习此类继承,以及基于此类继承的更炫酷的语言功能

属性 `[[Prototype]]` 是内部的而且是隐藏的,但是这儿有很多设置它的方式。

Expand All @@ -27,19 +27,11 @@ let rabbit = {
};

*!*
rabbit.__proto__ = animal;
rabbit.__proto__ = animal; // 设置 rabbit.[[Prototype]] = animal
*/!*
```

```smart header="`__proto__` 是 `[[Prototype]]` 的因历史原因而留下来的 getter/setter"
请注意,`__proto__` 与 `[[Prototype]]` **不一样**。`__proto__` 是 `[[Prototype]]` 的 getter/setter。

`__proto__` 的存在是历史的原因。在现代编程语言中,将其替换为函数 `Object.getPrototypeOf/Object.setPrototypeOf` 也能 get/set 原型。我们稍后将学习造成这种情况的原因以及这些函数。

根据规范,`__proto__` 必须仅在浏览器环境下才能得到支持,但实际上,包括服务端在内的所有环境都支持它。目前,由于 `__proto__` 标记在观感上更加明显,所以我们在后面的示例中将使用它。
```

如果我们在 `rabbit` 中查找一个缺失的属性,JavaScript 会自动从 `animal` 中获取它。
现在,如果我们从 `rabbit` 中读取一个它没有的属性,JavaScript 会自动从 `animal` 中获取。

例如:

Expand Down Expand Up @@ -130,13 +122,28 @@ alert(longEar.jumps); // true(从 rabbit)

![](proto-animal-rabbit-chain.svg)

现在,如果我们从 `longEar` 中读取一些它不存在的内容,JavaScript 会先在 `rabbit` 中查找,然后在 `animal` 中查找。

这里只有两个限制:

1. 引用不能形成闭环。如果我们试图在一个闭环中分配 `__proto__`,JavaScript 会抛出错误。
2. `__proto__` 的值可以是对象,也可以是 `null`。而其他的类型都会被忽略。

当然,这可能很显而易见,但是仍然要强调:只能有一个 `[[Prototype]]`。一个对象不能从其他两个对象获得继承。


```smart header="`__proto__` 是 `[[Prototype]]` 的因历史原因而留下来的 getter/setter"
初学者常犯一个普遍的错误,就是不知道 `__proto__` 和 `[[Prototype]]` 的区别。

请注意,`__proto__` 与内部的 `[[Prototype]]` **不一样**。`__proto__` 是 `[[Prototype]]` 的 getter/setter。稍后,我们将看到在什么情况下理解它们很重要,在建立对 JavaScript 语言的理解时,让我们牢记这一点。

`__proto__` 属性有点过时了。它的存在是出于历史的原因,现代编程语言建议我们应该使用函数 `Object.getPrototypeOf/Object.setPrototypeOf` 来取代 `__proto__` 去 get/set 原型。稍后我们将介绍这些函数。

根据规范,`__proto__` 必须仅受浏览器环境的支持。但实际上,包括服务端在内的所有环境都支持它,因此我们使用它是非常安全的。

由于 `__proto__` 标记在观感上更加明显,所以我们在后面的示例中将使用它。
```

## 写入不使用原型

原型仅用于读取属性。
Expand Down
Loading