From d6dd34008b8bdd8284efa62b32095020b18b4101 Mon Sep 17 00:00:00 2001 From: LeviDing Date: Sat, 18 Jul 2020 12:18:38 +0800 Subject: [PATCH 1/2] update some chacters --- .../04-prototype-methods/article.md | 7 +- 1-js/09-classes/01-class/article.md | 95 ++++++++-------- .../02-class-inheritance/article.md | 102 +++++++++++++++++- .../rabbit-extends-object.svg | 0 .../3-class-extend-object/solution.md | 0 .../3-class-extend-object/task.md | 0 1-js/09-classes/07-mixins/article.md | 2 +- 1-js/09-classes/07-mixins/head.html | 2 +- 1-js/10-error-handling/1-try-catch/article.md | 32 +++--- 1-js/11-async/05-promise-api/article.md | 8 +- .../2-async-iterators-generators/article.md | 2 +- .../2-async-iterators-generators/head.html | 2 +- 12 files changed, 173 insertions(+), 79 deletions(-) rename 1-js/09-classes/{02-class-inheritance => 03-static-properties-methods}/3-class-extend-object/rabbit-extends-object.svg (100%) rename 1-js/09-classes/{02-class-inheritance => 03-static-properties-methods}/3-class-extend-object/solution.md (100%) rename 1-js/09-classes/{02-class-inheritance => 03-static-properties-methods}/3-class-extend-object/task.md (100%) diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index b21172ee97..1980fdf9a7 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -57,7 +57,6 @@ alert(rabbit.jumps); // true 我们可以使用 `Object.create` 来实现比复制 `for..in` 循环中的属性更强大的对象克隆方式: ```js -// 完全相同的对象 obj 的浅拷贝 let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ``` @@ -106,7 +105,7 @@ alert(obj[key]); // [object Object],并不是 "some value"! 我们不应该对此感到惊讶。`__proto__` 属性很特别:它必须是对象或者 `null`。字符串不能成为 prototype。 -但是我们不是 **打算** 实现这种行为,对吗?我们想要存储键值对,然而键名为 `"__proto__"` 的键值对没有被正确存储。所以这是一个 bug。 +但是我们不是 **打算** 实现这种行为,对吧?我们想要存储键值对,然而键名为 `"__proto__"` 的键值对没有被正确存储。所以这是一个 bug。 在这里,后果并没有很严重。但是在其他情况下,我们可能会对对象进行赋值操作,然后原型可能就被更改了。结果,可能会导致完全意想不到的结果。 @@ -116,7 +115,7 @@ alert(obj[key]); // [object Object],并不是 "some value"! 我们怎么避免这样的问题呢? -首先,我们可以改用 `Map`,那么一切都迎刃而解。 +首先,我们可以改用 `Map` 代替普通对象来进行存储,那么一切都迎刃而解。 但是 `Object` 在这里同样可以运行得很好,因为 JavaScript 语言的制造者很早就注意到了这个问题。 @@ -128,7 +127,7 @@ alert(obj[key]); // [object Object],并不是 "some value"! 就像在本部分教程的开头所说的那样:`__proto__` 是一种访问 `[[Prototype]]` 的方式,而不是 `[[prototype]]` 本身。 -现在,我们想要将一个对象用作关联数组,我们可以使用一些小技巧: +现在,如果我们打算将一个对象用作关联数组,并且想摆脱这种问题,我们可以使用一些小技巧: ```js run *!* diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index 145b069147..a9026b1706 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -106,7 +106,7 @@ class User { // class 是函数 function alert(typeof User); // function -// ...或者,更确切地说,是构造器方法 +// ...或者,更确切地说,是 constructor 方法 alert(User === User.prototype.constructor); // true // 方法在 User.prototype 中,例如: @@ -127,7 +127,7 @@ alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi function User(name) { this.name = name; } -// 任何函数原型默认都具有构造器属性, +// 函数的原型(prototype)默认具有 "constructor" 属性, // 所以,我们不需要创建它 // 2. 将方法添加到原型 @@ -146,7 +146,7 @@ user.sayHi(); 1. 首先,通过 `class` 创建的函数具有特殊的内部属性标记 `[[FunctionKind]]:"classConstructor"`。因此,它与手动创建并不完全相同。 - 不像普通函数,调用类构造器时必须要用 `new` 关键词: + 编程语言会在许多地方检查该属性。例如,与普通函数不同,必须使用 `new` 来调用它: ```js run class User { @@ -166,6 +166,7 @@ user.sayHi(); alert(User); // class User { ... } ``` + 还有其他的不同之处,我们很快就会看到。 2. 类方法不可枚举。 类定义将 `"prototype"` 中的所有方法的 `enumerable` 标志设置为 `false`。 @@ -209,7 +210,6 @@ new User().sayHi(); // 正常运行,显示 MyClass 中定义的内容 alert(MyClass); // error,MyClass 在外部不可见 ``` - 我们甚至可以动态地“按需”创建类,就像这样: ```js run @@ -229,7 +229,7 @@ new User().sayHi(); // Hello ``` -## Getters/setters 及其他速记 +## Getters/setters 就像对象字面量,类可能包括 getters/setters,计算属性(computed properties)等。 @@ -267,22 +267,11 @@ alert(user.name); // John user = new User(""); // Name is too short. ``` -类声明在 `User.prototype` 中创建 getters 和 setters,就像这样: +Technically, such class declaration works by creating getters and setters in `User.prototype`. -```js -Object.defineProperties(User.prototype, { - name: { - get() { - return this._name - }, - set(name) { - // ... - } - } -}); -``` +## Computed names [...] -这是一个 `[...]` 中有计算属性名称(computed property name)的例子: +Here's an example with a computed method name using brackets `[...]`: ```js run class User { @@ -298,13 +287,15 @@ class User { new User().sayHi(); ``` +Such features are easy to remember, as they resemble that of literal objects. + ## Class 字段 ```warn header="旧的浏览器可能需要 polyfill" 类字段(field)是最近才添加到语言中的。 ``` -之前,类仅具有方法。 +之前,我们的类仅具有方法。 “类字段”是一种允许添加任何属性的语法。 @@ -313,7 +304,7 @@ new User().sayHi(); ```js run class User { *!* - name = "Anonymous"; + name = "John"; */!* sayHi() { @@ -321,15 +312,38 @@ class User { } } -new User().sayHi(); +new User().sayHi(); // Hello, John! +``` + +So, we just write " = " in the declaration, and that's it. -alert(User.prototype.sayHi); // 被放在 User.prototype 中 -alert(User.prototype.name); // undefined,没有被放在 User.prototype 中 +The important difference of class fields is that they are set on individual objects, not `User.prototype`: + +```js run +class User { +*!* + name = "John"; +*/!* +} + +let user = new User(); +alert(user.name); // John +alert(User.prototype.name); // undefined ``` -关于类字段的重要一点是,它们设置在单个对象上的,而不是设置在 `User.prototype` 上的。 +We can also assign values using more complex expressions and function calls: + +```js run +class User { +*!* + name = prompt("Name, please?", "John"); +*/!* +} + +let user = new User(); +alert(user.name); // John +``` -从技术上讲,它们是在 constructor 完成工作后被处理的。 ### 使用类字段制作绑定方法 @@ -362,30 +376,9 @@ setTimeout(button.click, 1000); // undefined 我们在 一章中讲过,有两种可以修复它的方式: 1. 传递一个包装函数,例如 `setTimeout(() => button.click(), 1000)`。 -2. 将方法绑定到对象,例如在 constructor 中: - -```js run -class Button { - constructor(value) { - this.value = value; -*!* - this.click = this.click.bind(this); -*/!* - } - - click() { - alert(this.value); - } -} - -let button = new Button("hello"); - -*!* -setTimeout(button.click, 1000); // hello -*/!* -``` +2. 将方法绑定到对象,例如在 constructor 中。 -类字段为后一种解决方案提供了更优雅的语法: +类字段提供了另一种非常优雅的语法: ```js run class Button { @@ -404,9 +397,9 @@ let button = new Button("hello"); setTimeout(button.click, 1000); // hello ``` -类字段 `click = () => {...}` 在每个 `Button` 对象上创建一个独立的函数,并将 `this` 绑定到该对象上。然后,我们可以将 `button.click` 传递到任何地方,并且它会被以正确的 `this` 进行调用。 +The class field `click = () => {...}` is created on a per-object basis, there's a separate function for each `Button` object, with `this` inside it referencing that object. We can pass `button.click` around anywhere, and the value of `this` will always be correct. -这在浏览器环境中,当我们需要将一个方法设置为事件监听器时尤其有用。 +在浏览器环境中,它对于进行事件监听尤为有用。 ## 总结 diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 3e7887caa8..c7f1f63112 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -230,7 +230,9 @@ let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. 哎呦!我们得到了一个报错。现在我们没法新建 rabbit。是什么地方出错了? -简短的解释是:继承类的 constructor 必须调用 `super(...)`,并且 (!) 一定要在使用 `this` 之前调用。 +简短的解释是: + +**继承类的 constructor 必须调用 `super(...)`,并且 (!) 一定要在使用 `this` 之前调用。** ……但这是为什么呢?这里发生了什么?确实,这个要求看起来很奇怪。 @@ -243,7 +245,7 @@ let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. - 当通过 `new` 执行一个常规函数时,它将创建一个空对象,并将这个空对象赋值给 `this`。 - 但是当继承的 constructor 执行时,它不会执行此操作。它期望父类的 constructor 来完成这项工作。 -因此,派生的 constructor 必须调用 `super` 才能执行其父类(非派生的)的 constructor,否则 `this` 指向的那个对象将不会被创建。并且我们会收到一个报错。 +因此,派生的 constructor 必须调用 `super` 才能执行其父类(base)的 constructor,否则 `this` 指向的那个对象将不会被创建。并且我们会收到一个报错。 为了让 `Rabbit` 的 constructor 可以工作,它需要在使用 `this` 之前调用 `super()`,就像下面这样: @@ -279,6 +281,102 @@ alert(rabbit.earLength); // 10 ``` + +### Overriding class fields: a tricky note + +```warn header="Advanced note" +This note assumes you have a certain experience with classes, maybe in other programming languages. + +It provides better insight into the language and also explains the behavior that might be a source of bugs (but not very often). + +If you find it difficult to understand, just go on, continue reading, then return to it some time later. +``` + +We can override not only methods, but also class fields. + +Although, there's a tricky behavior when we access an overridden field in parent constructor, quite different from most other programming languages. + +Consider this example: + +```js run +class Animal { + name = 'animal' + + constructor() { + alert(this.name); // (*) + } +} + +class Rabbit extends Animal { + name = 'rabbit'; +} + +new Animal(); // animal +*!* +new Rabbit(); // animal +*/!* +``` + +Here, class `Rabbit` extends `Animal` and overrides `name` field with its own value. + +There's no own constructor in `Rabbit`, so `Animal` constructor is called. + +What's interesting is that in both cases: `new Animal()` and `new Rabbit()`, the `alert` in the line `(*)` shows `animal`. + +**In other words, parent constructor always uses its own field value, not the overridden one.** + +What's odd about it? + +If it's not clear yet, please compare with methods. + +Here's the same code, but instead of `this.name` field we call `this.showName()` method: + +```js run +class Animal { + showName() { // instead of this.name = 'animal' + alert('animal'); + } + + constructor() { + this.showName(); // instead of alert(this.name); + } +} + +class Rabbit extends Animal { + showName() { + alert('rabbit'); + } +} + +new Animal(); // animal +*!* +new Rabbit(); // rabbit +*/!* +``` + +Please note: now the output is different. + +And that's what we naturally expect. When the parent constructor is called in the derived class, it uses the overridden method. + +...But for class fields it's not so. As said, the parent constructor always uses the parent field. + +Why is there the difference? + +Well, the reason is in the field initialization order. The class field is initialized: +- Before constructor for the base class (that doesn't extend anything), +- Imediately after `super()` for the derived class. + +In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As said previously, that's the same as if there was an empty constructor with only `super(...args)`. + +So, `new Rabbit()` calls `super()`, thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no `Rabbit` class fields yet, that's why `Animal` fields are used. + +This subtle difference between fields and methods is specific to JavaScript + +Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what's going on, so we're explaining it here. + +If it becomes a problem, one can fix it by using methods or getters/setters instead of fields. + + ## 深入:内部探究和 [[HomeObject]] ```warn header="进阶内容" diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg similarity index 100% rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md similarity index 100% rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md similarity index 100% rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 36beef76e2..7a4893b6a8 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -140,7 +140,7 @@ let eventMixin = { * menu.off('select', handler) */ off(eventName, handler) { - let handlers = this._eventHandlers && this._eventHandlers[eventName]; + let handlers = this._eventHandlers?.[eventName]; if (!handlers) return; for (let i = 0; i < handlers.length; i++) { if (handlers[i] === handler) { diff --git a/1-js/09-classes/07-mixins/head.html b/1-js/09-classes/07-mixins/head.html index 77ea38b204..20e3a63547 100644 --- a/1-js/09-classes/07-mixins/head.html +++ b/1-js/09-classes/07-mixins/head.html @@ -18,7 +18,7 @@ * menu.off('select', handler) */ off(eventName, handler) { - let handlers = this._eventHandlers && this._eventHandlers[eventName]; + let handlers = this._eventHandlers?.[eventName]; if (!handlers) return; for(let i = 0; i < handlers.length; i++) { if (handlers[i] == handler) { diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index 6e58bd6cf6..d3af18907c 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -355,27 +355,31 @@ try { 在我们的例子中,`try..catch` 旨在捕获“数据不正确”的 error。但是实际上,catch 会捕获到 **所有** 来自于 `try` 的 error。在这儿,它捕获到了一个预料之外的 error,但是仍然抛出的是同样的 `"JSON Error"` 信息。这是不正确的,并且也会使代码变得更难以调试。 -幸运的是,我们可以通过其他方式找出我们捕获的是什么 error,例如通过它的 `name` 属性: +To avoid such problems, we can employ the "rethrowing" technique. The rule is simple: + +**`catch` 应该只处理它知道的 error,并“抛出”所有其他 error。** + +“再次抛出(rethrowing)”技术可以被更详细地解释为: + +1. Catch 捕获所有 error。 +2. 在 `catch(err) {...}` 块中,我们对 error 对象 `err` 进行分析。 +3. 如果我们不知道如何处理它,那我们就 `throw err`。 + +Usually, we can check the error type using the `instanceof` operator: ```js run try { user = { /*...*/ }; -} catch(e) { +} catch(err) { *!* - alert(e.name); // "ReferenceError" for accessing an undefined variable + if (err instanceof ReferenceError) { */!* + alert('ReferenceError'); // "ReferenceError" for accessing an undefined variable + } } ``` -规则很简单: - -**`catch` 应该只处理它知道的 error,并“抛出”所有其他 error。** - -“再次抛出(rethrowing)”技术可以被更详细地解释为: - -1. Catch 捕获所有 error。 -2. 在 `catch(err) {...}` 块中,我们对 error 对象 `err` 进行分析。 -3. 如果我们不知道如何处理它,那我们就 `throw err`。 +We can also get the error class name from `err.name` property. All native errors have it. Another option is to read `err.constructor.name`. 在下面的代码中,我们使用“再次抛出”,以达到在 `catch` 中只处理 `SyntaxError` 的目的: @@ -398,7 +402,7 @@ try { } catch(e) { *!* - if (e.name == "SyntaxError") { + if (e instanceof SyntaxError) { alert( "JSON Error: " + e.message ); } else { throw e; // 再次抛出 (*) @@ -425,7 +429,7 @@ function readData() { */!* } catch (e) { // ... - if (e.name != 'SyntaxError') { + if (!(e instanceof SyntaxError)) { *!* throw e; // 再次抛出(不知道如何处理它) */!* diff --git a/1-js/11-async/05-promise-api/article.md b/1-js/11-async/05-promise-api/article.md index 27767fd10b..45e4af2599 100644 --- a/1-js/11-async/05-promise-api/article.md +++ b/1-js/11-async/05-promise-api/article.md @@ -179,10 +179,10 @@ Promise.allSettled(urls.map(url => fetch(url))) if(!Promise.allSettled) { Promise.allSettled = function(promises) { return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({ - state: 'fulfilled', + status: 'fulfilled', value }), reason => ({ - state: 'rejected', + status: 'rejected', reason })))); }; @@ -191,7 +191,7 @@ if(!Promise.allSettled) { 在这段代码中,`promises.map` 获取输入值,并通过 `p => Promise.resolve(p)` 将输入值转换为 promise(以防传递了 non-promise),然后向每一个 promise 都添加 `.then` 处理程序(handler)。 -这个处理程序(handler)将成功的结果 `value` 转换为 `{state:'fulfilled', value}`,将 error `reason` 转换为 `{state:'rejected', reason}`。这正是 `Promise.allSettled` 的格式。 +这个处理程序(handler)将成功的结果 `value` 转换为 `{status:'fulfilled', value}`,将 error `reason` 转换为 `{status:'rejected', reason}`。这正是 `Promise.allSettled` 的格式。 然后我们就可以使用 `Promise.allSettled` 来获取 **所有** 给定的 promise 的结果,即使其中一些被 reject。 @@ -277,7 +277,7 @@ let promise = new Promise((resolve, reject) => reject(error)); 1. `Promise.all(promises)` —— 等待所有 promise 都 resolve 时,返回存放它们结果的数组。如果给定的任意一个 promise 为 reject,那么它就会变成 `Promise.all` 的 error,所有其他 promise 的结果都会被忽略。 2. `Promise.allSettled(promises)`(ES2020 新增方法)—— 等待所有 promise 都 settle 时,并以包含以下内容的对象数组的形式返回它们的结果: - - `state`: `"fulfilled"` 或 `"rejected"` + - `status`: `"fulfilled"` 或 `"rejected"` - `value`(如果 fulfilled)或 `reason`(如果 rejected)。 3. `Promise.race(promises)` —— 等待第一个 settle 的 promise,并将其 result/error 作为结果。 4. `Promise.resolve(value)` —— 使用给定 value 创建一个 resolved 的 promise。 diff --git a/1-js/12-generators-iterators/2-async-iterators-generators/article.md b/1-js/12-generators-iterators/2-async-iterators-generators/article.md index 873090f68e..7c13c6365c 100644 --- a/1-js/12-generators-iterators/2-async-iterators-generators/article.md +++ b/1-js/12-generators-iterators/2-async-iterators-generators/article.md @@ -301,7 +301,7 @@ async function* fetchCommits(repo) { // (3) 前往下一页的 URL 在 header 中,提取它 let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/); - nextPage = nextPage && nextPage[1]; + nextPage = nextPage?.[1]; url = nextPage; diff --git a/1-js/12-generators-iterators/2-async-iterators-generators/head.html b/1-js/12-generators-iterators/2-async-iterators-generators/head.html index 74d66a8b8c..03f21e2bd8 100644 --- a/1-js/12-generators-iterators/2-async-iterators-generators/head.html +++ b/1-js/12-generators-iterators/2-async-iterators-generators/head.html @@ -11,7 +11,7 @@ // the URL of the next page is in the headers, extract it let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/); - nextPage = nextPage && nextPage[1]; + nextPage = nextPage?.[1]; url = nextPage; From c404ab1090a0a7ba8981b21e6ac19fc91a226b07 Mon Sep 17 00:00:00 2001 From: LeviDing Date: Sat, 18 Jul 2020 12:48:45 +0800 Subject: [PATCH 2/2] update some chacters in 1-js --- 1-js/13-modules/01-modules-intro/article.md | 12 +- 1-js/13-modules/02-import-export/article.md | 12 +- 1-js/99-js-misc/01-proxy/article.md | 2 +- .../2-check-syntax/solution.md | 0 .../04-reference-type}/2-check-syntax/task.md | 0 .../04-reference-type}/3-why-this/solution.md | 0 .../04-reference-type}/3-why-this/task.md | 0 1-js/99-js-misc/04-reference-type/article.md | 114 ++++++++++++++++++ 8 files changed, 129 insertions(+), 11 deletions(-) rename 1-js/{04-object-basics/04-object-methods => 99-js-misc/04-reference-type}/2-check-syntax/solution.md (100%) rename 1-js/{04-object-basics/04-object-methods => 99-js-misc/04-reference-type}/2-check-syntax/task.md (100%) rename 1-js/{04-object-basics/04-object-methods => 99-js-misc/04-reference-type}/3-why-this/solution.md (100%) rename 1-js/{04-object-basics/04-object-methods => 99-js-misc/04-reference-type}/3-why-this/task.md (100%) create mode 100644 1-js/99-js-misc/04-reference-type/article.md diff --git a/1-js/13-modules/01-modules-intro/article.md b/1-js/13-modules/01-modules-intro/article.md index bcdcfeedc5..940091e384 100755 --- a/1-js/13-modules/01-modules-intro/article.md +++ b/1-js/13-modules/01-modules-intro/article.md @@ -1,13 +1,13 @@ # 模块 (Module) 简介 -随着我们的应用越来越大,我们想要将其拆分成多个文件,即所谓的“模块(module)”。一个模块通常包含一个类或一个函数库。 +随着我们的应用越来越大,我们想要将其拆分成多个文件,即所谓的“模块(module)”。一个模块可以包含用于特定目的的类或函数库。 很长一段时间,JavaScript 都没有语言级(language-level)的模块语法。这不是一个问题,因为最初的脚本又小又简单,所以没必要将其模块化。 但是最终脚本变得越来越复杂,因此社区发明了许多种方法来将代码组织到模块中,使用特殊的库按需加载模块。 -例如: +列举一些(出于历史原因): - [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) — 最古老的模块系统之一,最初由 [require.js](http://requirejs.org/) 库实现。 - [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) — 为 Node.js 服务器创建的模块系统。 @@ -15,11 +15,11 @@ 现在,所有他们都在慢慢成为历史的一部分,但我们仍然可以在旧脚本中找到它们。 -语言级的模块系统在 2015 年的时候出现在了标准(ES6)中,此后逐渐发展,现在已经得到了所有主流浏览器和 Node.js 的支持。因此,我们将从现在开始学习它们。 +语言级的模块系统在 2015 年的时候出现在了标准(ES6)中,此后逐渐发展,现在已经得到了所有主流浏览器和 Node.js 的支持。因此,我们将从现在开始学习现代 JavaScript 模块(module)。 ## 什么是模块? -一个模块(module)就是一个文件。一个脚本就是一个模块。 +一个模块(module)就是一个文件。一个脚本就是一个模块。就这么简单。 模块可以相互加载,并可以使用特殊的指令 `export` 和 `import` 来交换功能,从另一个模块调用一个模块的函数: @@ -57,6 +57,10 @@ sayHi('John'); // Hello, John! 浏览器会自动获取并解析(evaluate)导入的模块(如果需要,还可以分析该模块的导入),然后运行该脚本。 +```warn header="Modules work only via HTTP(s), not in local files" +If you try to open a web-page locally, via `file://` protocol, you'll find that `import/export` directives don't work. Use a local web-server, such as [static-server](https://www.npmjs.com/package/static-server#getting-started) or use the "live server" capability of your editor, such as VS Code [Live Server Extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) to test modules. +``` + ## 模块核心功能 与“常规”脚本相比,模块有什么不同呢? diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index e8948341be..87d3b20e7e 100755 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -2,7 +2,7 @@ 导出(export)和导入(import)指令有几种语法变体。 -在上一章,我们看到了一个简单的用法,现在让我们来探索更多示例吧。 +在上一节,我们看到了一个简单的用法,现在让我们来探索更多示例吧。 ## 在声明前导出 @@ -162,7 +162,7 @@ say.*!*bye*/!*('John'); // Bye, John! 当然,这需要大量文件,因为每个东西都需要自己的模块,但这根本不是问题。实际上,如果文件具有良好的命名,并且文件夹结构得当,那么代码导航(navigation)会变得更容易。 -模块提供了特殊的默认导出 `export default` 语法,以使“一个模块只做一件事”的方式看起来更好。 +模块提供了一个特殊的默认导出 `export default` 语法,以使“一个模块只做一件事”的方式看起来更好。 将 `export default` 放在要导出的实体前: @@ -241,7 +241,7 @@ function sayHi(user) { export {sayHi as default}; ``` -或者,另一种情况,假设模块 `user.js` 导出了一个主要的默认的导出和一些命名的导出(虽然很少出现,但是会发生): +或者,另一种情况,假设模块 `user.js` 导出了一个主要的默认的导出和一些命名的导出(这种情况很少见,但确实会发生): ```js // 📁 user.js @@ -321,7 +321,7 @@ export {default as User} from './user.js'; // 重新导出 default 为什么要这样做?我们看一个实际开发中的用例。 -想象一下,我们正在编写一个 "package":一个包含大量模块的文件夹,其中一些功能是导出到外部的(像 NPM 这样的工具允许发布和分发这样的 package),并且其中一些模块仅仅是供其他 package 中的模块内部使用的 "helpers"。 +想象一下,我们正在编写一个 "package":一个包含大量模块的文件夹,其中一些功能是导出到外部的(像 NPM 这样的工具允许我们发布和分发这样的 package),并且其中一些模块仅仅是供其他 package 中的模块内部使用的 "helpers"。 文件结构可能是这样的: ``` @@ -403,7 +403,7 @@ export default class User { ## 总结 -这是我们在本章和前面章节中介绍的所有 `export` 类型: +这是我们在本节和前面章节中介绍的所有 `export` 类型: 你可以阅读并回忆它们的含义来进行自查: @@ -452,4 +452,4 @@ if (something) { ……但是,如果我们真的需要根据某些条件来进行导入呢?或者在某些合适的时间?例如,根据请求(request)加载模块,什么时候才是真正需要呢? -我们将在下一章中学习动态导入。 +我们将在下一章节中学习动态导入。 diff --git a/1-js/99-js-misc/01-proxy/article.md b/1-js/99-js-misc/01-proxy/article.md index d22746620e..5beffcceaf 100644 --- a/1-js/99-js-misc/01-proxy/article.md +++ b/1-js/99-js-misc/01-proxy/article.md @@ -662,7 +662,7 @@ user.name = "Pete"; // 显示 "SET name=Pete" ### 代理一个 getter -让我们看一个示例,来说明为什么 `Reflect.get` 更好。此外,我们还将看到为什么 `get/set` 有第四个参数 `receiver`,而且我们之前从来没有使用过它。 +让我们看一个示例,来说明为什么 `Reflect.get` 更好。此外,我们还将看到为什么 `get/set` 有第三个参数 `receiver`,而且我们之前从来没有使用过它。 我们有一个带有 `_name` 属性和 getter 的对象 `user`。 diff --git a/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md b/1-js/99-js-misc/04-reference-type/2-check-syntax/solution.md similarity index 100% rename from 1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md rename to 1-js/99-js-misc/04-reference-type/2-check-syntax/solution.md diff --git a/1-js/04-object-basics/04-object-methods/2-check-syntax/task.md b/1-js/99-js-misc/04-reference-type/2-check-syntax/task.md similarity index 100% rename from 1-js/04-object-basics/04-object-methods/2-check-syntax/task.md rename to 1-js/99-js-misc/04-reference-type/2-check-syntax/task.md diff --git a/1-js/04-object-basics/04-object-methods/3-why-this/solution.md b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md similarity index 100% rename from 1-js/04-object-basics/04-object-methods/3-why-this/solution.md rename to 1-js/99-js-misc/04-reference-type/3-why-this/solution.md diff --git a/1-js/04-object-basics/04-object-methods/3-why-this/task.md b/1-js/99-js-misc/04-reference-type/3-why-this/task.md similarity index 100% rename from 1-js/04-object-basics/04-object-methods/3-why-this/task.md rename to 1-js/99-js-misc/04-reference-type/3-why-this/task.md diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md new file mode 100644 index 0000000000..ef843ab79f --- /dev/null +++ b/1-js/99-js-misc/04-reference-type/article.md @@ -0,0 +1,114 @@ + +# Reference Type + +```warn header="In-depth language feature" +This article covers an advanced topic, to understand certain edge-cases better. + +It's not important. Many experienced developers live fine without knowing it. Read on if you're want to know how things work under the hood. +``` + +A dynamically evaluated method call can lose `this`. + +For instance: + +```js run +let user = { + name: "John", + hi() { alert(this.name); }, + bye() { alert("Bye"); } +}; + +user.hi(); // works + +// now let's call user.hi or user.bye depending on the name +*!* +(user.name == "John" ? user.hi : user.bye)(); // Error! +*/!* +``` + +On the last line there is a conditional operator that chooses either `user.hi` or `user.bye`. In this case the result is `user.hi`. + +Then the method is immediately called with parentheses `()`. But it doesn't work correctly! + +As you can see, the call results in an error, because the value of `"this"` inside the call becomes `undefined`. + +This works (object dot method): +```js +user.hi(); +``` + +This doesn't (evaluated method): +```js +(user.name == "John" ? user.hi : user.bye)(); // Error! +``` + +Why? If we want to understand why it happens, let's get under the hood of how `obj.method()` call works. + +## Reference type explained + +Looking closely, we may notice two operations in `obj.method()` statement: + +1. First, the dot `'.'` retrieves the property `obj.method`. +2. Then parentheses `()` execute it. + +So, how does the information about `this` get passed from the first part to the second one? + +If we put these operations on separate lines, then `this` will be lost for sure: + +```js run +let user = { + name: "John", + hi() { alert(this.name); } +} + +*!* +// split getting and calling the method in two lines +let hi = user.hi; +hi(); // Error, because this is undefined +*/!* +``` + +Here `hi = user.hi` puts the function into the variable, and then on the last line it is completely standalone, and so there's no `this`. + +**To make `user.hi()` calls work, JavaScript uses a trick -- the dot `'.'` returns not a function, but a value of the special [Reference Type](https://tc39.github.io/ecma262/#sec-reference-specification-type).** + +The Reference Type is a "specification type". We can't explicitly use it, but it is used internally by the language. + +The value of Reference Type is a three-value combination `(base, name, strict)`, where: + +- `base` is the object. +- `name` is the property name. +- `strict` is true if `use strict` is in effect. + +The result of a property access `user.hi` is not a function, but a value of Reference Type. For `user.hi` in strict mode it is: + +```js +// Reference Type value +(user, "hi", true) +``` + +When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`=user` in this case). + +Reference type is a special "intermediary" internal type, with the purpose to pass information from dot `.` to calling parentheses `()`. + +Any other operation like assignment `hi = user.hi` discards the reference type as a whole, takes the value of `user.hi` (a function) and passes it on. So any further operation "loses" `this`. + +So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). Later in this tutorial, we will learn various ways to solve this problem such as [func.bind()](/bind#solution-2-bind). + +## Summary + +Reference Type is an internal type of the language. + +Reading a property, such as with dot `.` in `obj.method()` returns not exactly the property value, but a special "reference type" value that stores both the property value and the object it was taken from. + +That's for the subsequent method call `()` to get the object and set `this` to it. + +For all other operations, the reference type automatically becomes the property value (a function in our case). + +The whole mechanics is hidden from our eyes. It only matters in subtle cases, such as when a method is obtained dynamically from the object, using an expression. + + + + + + result of dot `.` isn't actually a method, but a value of `` needs a way to pass the information about `obj`