From b0981450250078e1df1e0c48972c277ca7040c54 Mon Sep 17 00:00:00 2001 From: LeviDing Date: Wed, 12 Jun 2019 09:21:46 +0800 Subject: [PATCH 01/10] add first draft of translation --- .../02-class-inheritance/article.md | 421 ++++++++++-------- 1 file changed, 227 insertions(+), 194 deletions(-) diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index abb7d1e334..8989dfc51e 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -1,103 +1,57 @@ -# Class inheritance +# 类继承和 super -Let's say we have two classes. +类可以继承另外一个类。这是一个非常棒的语法,在技术上是它基于原型继承实现的。 -`Animal`: - -```js -class Animal { - constructor(name) { - this.speed = 0; - this.name = name; - } - run(speed) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); - } - stop() { - this.speed = 0; - alert(`${this.name} stopped.`); - } -} - -let animal = new Animal("My animal"); -``` - -![](rabbit-animal-independent-animal.png) - - -...And `Rabbit`: - -```js -class Rabbit { - constructor(name) { - this.name = name; - } - hide() { - alert(`${this.name} hides!`); - } -} - -let rabbit = new Rabbit("My rabbit"); -``` - -![](rabbit-animal-independent-rabbit.png) +为了继承另外一个类,我们需要在括号 `{..}` 前指定 `"extends"` 和父类 - -Right now they are fully independent. - -But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods. - -To inherit from another class, we should specify `"extends"` and the parent class before the braces `{..}`. - -Here `Rabbit` inherits from `Animal`: +这里我们写一个继承自 `Animal` 的 `Rabbit`: ```js run class Animal { + constructor(name) { this.speed = 0; this.name = name; } + run(speed) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } + stop() { this.speed = 0; alert(`${this.name} stopped.`); } + } -// Inherit from Animal by specifying "extends Animal" *!* +// 从 Animal 继承 class Rabbit extends Animal { -*/!* hide() { alert(`${this.name} hides!`); } } +*/!* let rabbit = new Rabbit("White Rabbit"); -rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.hide(); // White Rabbit hides! +rabbit.run(5); // 白色兔子会以速度 5 奔跑。 +rabbit.hide(); // 白色兔子藏了起来! ``` -Now the `Rabbit` code became a bit shorter, as it uses `Animal` constructor by default, and it also can `run`, as animals do. - -Internally, `extends` keyword adds `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`: +就如你期望的那样,也正如我们之前所见,`extends` 关键字实际上是给 `Rabbit.prototype` 添加了一个属性 `[[Prototype]]`,并且它会指向 `Animal.prototype`。 ![](animal-rabbit-extends.png) -So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`. - -As we can recall from the chapter , JavaScript uses the same prototypal inheritance for build-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`, so dates have generic object methods. +所以,现在 `rabbit` 即可以访问它自己的方法,也可以访问 `Animal` 的方法。 ````smart header="Any expression is allowed after `extends`" -Class syntax allows to specify not just a class, but any expression after `extends`. +类语法不仅允许在 `extends` 后指定一个类,也允许指定任何表达式。 -For instance, a function call that generates the parent class: +举个例子,通过一个函数调用生成父类: ```js run function f(phrase) { @@ -112,34 +66,33 @@ class User extends f("Hello") {} new User().sayHi(); // Hello ``` -Here `class User` inherits from the result of `f("Hello")`. +这里 `class User` 继承自 `f("Hello")` 的执行结果。 -That may be useful for advanced programming patterns when we use functions to generate classes depending on many conditions and can inherit from them. +对于一些高级的编程模式来说这可能会很有用,比如我们可以使用函数来根据许多条件生成不同的类,并可以从中继承。 ```` -## Overriding a method +## 重写一个方法 -Now let's move forward and override a method. As of now, `Rabbit` inherits the `stop` method that sets `this.speed = 0` from `Animal`. +现在让我们继续前进并尝试重写一个方法。到目前为止,`Rabbit` 继承了 `Animal` 的 `stop` 方法,该方法设置了 `this.speed = 0`。 -If we specify our own `stop` in `Rabbit`, then it will be used instead: +如果我们在 `Rabbit` 中定义了我们自己的 `stop` 方法,那么它将被用来代替 `Animal` 的 `stop` 方法: ```js class Rabbit extends Animal { stop() { - // ...this will be used for rabbit.stop() + // ...这将用于 rabbit.stop() } } ``` +...但是通常来说,我们不希望完全替换父类的方法,而是希望基于它做一些调整或者扩展。我们在我们的方法中做一些事情,但是在它之前/之后或在执行过程中调用父类的方法。 -...But usually we don't want to totally replace a parent method, but rather to build on top of it, tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. +为此,类提供了 `"super"` 关键字。 -Classes provide `"super"` keyword for that. +- 执行 `super.method(...)` 来调用一个父类的方法。 +- 执行 `super(...)` 调用父类的构造函数 (只能在子类的构造函数中执行)。 -- `super.method(...)` to call a parent method. -- `super(...)` to call a parent constructor (inside our constructor only). - -For instance, let our rabbit autohide when stopped: +例如,让我们的兔子在停下时自动隐藏: ```js run class Animal { @@ -168,8 +121,8 @@ class Rabbit extends Animal { *!* stop() { - super.stop(); // call parent stop - this.hide(); // and then hide + super.stop(); // 调用父类的 stop 函数 + this.hide(); // 并且在那之后隐藏 } */!* } @@ -180,40 +133,41 @@ rabbit.run(5); // White Rabbit runs with speed 5. rabbit.stop(); // White Rabbit stopped. White rabbit hides! ``` -Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the process. +现在,`Rabbit` 有自己的 `stop` 函数,并且在执行过程中会调用父类的 `super.stop()`。 ````smart header="Arrow functions have no `super`" -As was mentioned in the chapter , arrow functions do not have `super`. +就像在箭头函数 那一章节所提到的,箭头函数没有 `super`。 + +如果被访问,它将从外部函数获取。举个例子: -If accessed, it's taken from the outer function. For instance: ```js class Rabbit extends Animal { stop() { - setTimeout(() => super.stop(), 1000); // call parent stop after 1sec + setTimeout(() => super.stop(), 1000); // 1 秒后调用父类的 stop 函数 } } ``` -The `super` in the arrow function is the same as in `stop()`, so it works as intended. If we specified a "regular" function here, there would be an error: +箭头函数中的 `super` 与 `stop()` 中的相同,所以它能按预期工作。如果我们在这里指定的是一个“普通”函数,那么将会抛出一个错误: ```js -// Unexpected super +// 未定义的 super setTimeout(function() { super.stop() }, 1000); ``` ```` -## Overriding constructor +## 重写构造函数 -With constructors it gets a little bit tricky. +对于构造函数来说,重写则有点棘手。 -Till now, `Rabbit` did not have its own `constructor`. +到目前为止,`Rabbit` 还没有自己的 `constructor`。 -According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following "empty" `constructor` is generated: +根据[规范](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation),如果一个类继承了另一个类并且没有构造函数,那么将生成以下构造函数: ```js class Rabbit extends Animal { - // generated for extending classes without own constructors + // 为没有构造函数的继承类生成以下的构造函数 *!* constructor(...args) { super(...args); @@ -222,9 +176,9 @@ class Rabbit extends Animal { } ``` -As we can see, it basically calls the parent `constructor` passing it all the arguments. That happens if we don't write a constructor of our own. +我们可以看到,它调用了父类的 `constructor` 并传递了所有的参数。如果我们不写自己的构造函数,就会出现这种情况。 -Now let's add a custom constructor to `Rabbit`. It will specify the `earLength` in addition to `name`: +现在让我们给 `Rabbit` 增加一个自定义的构造函数。除了 `name` 它还会定义 `earLength`: ```js run class Animal { @@ -249,29 +203,29 @@ class Rabbit extends Animal { } *!* -// Doesn't work! +// 不生效! let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. */!* ``` -Whoops! We've got an error. Now we can't create rabbits. What went wrong? +哎呦!我们得到一个报错。现在我们没法新建兔子了。是什么地方出错了? -The short answer is: constructors in inheriting classes must call `super(...)`, and (!) do it before using `this`. +简短的解释下原因:继承类的构造函数必须调用 `super(...)`,并且一定要在使用 `this` 之前调用。 -...But why? What's going on here? Indeed, the requirement seems strange. +...但这是为什么呢?这里发生了什么?这个要求确实看起来很奇怪。 -Of course, there's an explanation. Let's get into details, so you'd really understand what's going on. +当然,本文会给出一个解释。让我们深入细节,这样你就可以真正的理解发生了什么。 -In JavaScript, there's a distinction between a "constructor function of an inheriting class" and all others. In an inheriting class, the corresponding constructor function is labelled with a special internal property `[[ConstructorKind]]:"derived"`. +在 JavaScript 中,“派生类的构造函数”与所有其他的构造函数之间存在区别。在派生类中,相应的构造函数会被标记为特殊的内部属性 `[[ConstructorKind]]:"derived"`。 -The difference is: +不同点就在于: -- When a normal constructor runs, it creates an empty object as `this` and continues with it. -- But when a derived constructor runs, it doesn't do it. It expects the parent constructor to do this job. +- 当一个普通构造函数执行时,它会创建一个空对象作为 `this` 并继续执行。 +- 但是当派生的构造函数执行时,它并不会做这件事。它期望父类的构造函数来完成这项工作。 -So if we're making a constructor of our own, then we must call `super`, because otherwise the object with `this` reference to it won't be created. And we'll get an error. +因此,如果我们构建了我们自己的构造函数,我们必须调用 `super`,因为否则的话 `this` 指向的对象不会被创建,并且我们会收到一个报错。 -For `Rabbit` to work, we need to call `super()` before using `this`, like here: +为了让 `Rabbit` 可以运行,我们需要在使用 `this` 之前调用 `super()`,就像下面这样: ```js run class Animal { @@ -305,19 +259,19 @@ alert(rabbit.earLength); // 10 ``` -## Super: internals, [[HomeObject]] +## Super: 内部基于 [[HomeObject]] 实现 -Let's get a little deeper under the hood of `super`. We'll see some interesting things by the way. +让我们再深入的去研究下 `super`。顺便说一句,我们会发现一些有趣的事情。 -First to say, from all that we've learned till now, it's impossible for `super` to work at all! +首先要说的是,从我们迄今为止学到的知识来看,`super` 是不可能运行的。 -Yeah, indeed, let's ask ourselves, how it could technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, it needs to retrieve the `method` from the prototype of the current object. +的确是这样,让我们问问自己,在技术上它是如何实现的?当一个对象方法运行时,它会将当前对象作为 `this`。如果之后我们调用 `super.method()`,那么如何检索 `method`?我们想当然地认为需要从当前对象的原型中获取 `method`。但是从技术上讲,我们(或者 JavaScript 的引擎)可以做到这一点吗? -The task may seem simple, but it isn't. The engine knows the current object `this`, so it could get the parent `method` as `this.__proto__.method`. Unfortunately, such a "naive" solution won't work. +也许我们可以从 `this` 的 `[[Prototype]]` 上获得方法,就像 `this.__proto__.method`?不幸的是,这样是行不通的。 -Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity. +让我们尝试去这么做看看。简单起见,我们不使用类,只使用普通对象。 -In the example below, `rabbit.__proto__ = animal`. Now let's try: in `rabbit.eat()` we'll call `animal.eat()`, using `this.__proto__`: +在这里,`rabbit.eat()` 会调用父对象的 `animal.eat()` 方法: ```js run let animal = { @@ -332,7 +286,7 @@ let rabbit = { name: "Rabbit", eat() { *!* - // that's how super.eat() could presumably work + // 这是 super.eat() 可能工作的原因 this.__proto__.eat.call(this); // (*) */!* } @@ -341,11 +295,11 @@ let rabbit = { rabbit.eat(); // Rabbit eats. ``` -At the line `(*)` we take `eat` from the prototype (`animal`) and call it in the context of the current object. Please note that `.call(this)` is important here, because a simple `this.__proto__.eat()` would execute parent `eat` in the context of the prototype, not the current object. +在 `(*)` 这一行,我们从 `animal` 的原型上获取 `eat`,并在当前对象的上下文中调用它。请注意,`.call(this)` 在这里非常重要,因为简单的调用 `this.__proto__.eat()` 将在原型的上下文中执行 `eat`,而非当前对象 -And in the code above it actually works as intended: we have the correct `alert`. +在上述的代码中,它按照期望运行:我们获得了正确的 `alert`。 -Now let's add one more object to the chain. We'll see how things break: +现在让我们在原型链上再添加一个额外的对象。我们将看到这件事是如何被打破的: ```js run let animal = { @@ -358,7 +312,7 @@ let animal = { let rabbit = { __proto__: animal, eat() { - // ...bounce around rabbit-style and call parent (animal) method + // ...新建一个兔子并调用父类的方法 this.__proto__.eat.call(this); // (*) } }; @@ -366,7 +320,7 @@ let rabbit = { let longEar = { __proto__: rabbit, eat() { - // ...do something with long ears and call parent (rabbit) method + // ...用长耳朵做一些事情,并调用父类(rabbit)的方法 this.__proto__.eat.call(this); // (**) } }; @@ -376,54 +330,57 @@ longEar.eat(); // Error: Maximum call stack size exceeded */!* ``` -The code doesn't work anymore! We can see the error trying to call `longEar.eat()`. +代码无法再运行了!我们可以看到,在试图调用 `longEar.eat()` 时抛出了错误。 -It may be not that obvious, but if we trace `longEar.eat()` call, then we can see why. In both lines `(*)` and `(**)` the value of `this` is the current object (`longEar`). That's essential: all object methods get the current object as `this`, not a prototype or something. +原因可能不那么明显,但是如果我们跟踪 `longEar.eat()` 的调用,就可以发现原因。在 `(*)` 和 `(**)` 这两行中,`this` 的值都是当前对象 `longEar`。这是至关重要的一点:所有的对象方法都将当前对象作为 `this`,而非原型或其他什么东西。 -So, in both lines `(*)` and `(**)` the value of `this.__proto__` is exactly the same: `rabbit`. They both call `rabbit.eat` without going up the chain in the endless loop. +因此,在 `(*)` 和 `(**)` 这两行中,`this.__proto__` 的值是完全相同的,都是 `rabbit`。在这个无限循环中,他们都调用了 `rabbit.eat`,而不是在原型链上向上寻找方法。 -Here's the picture of what happens: +这张图介绍了发生的情况: ![](this-super-loop.png) -1. Inside `longEar.eat()`, the line `(**)` calls `rabbit.eat` providing it with `this=longEar`. +1. 在 `longEar.eat()` 中,`(**)` 这一行调用 `rabbit.eat` 并且此时 `this=longEar`. ```js - // inside longEar.eat() we have this = longEar + // 在 longEar.eat() 中 this 指向 longEar this.__proto__.eat.call(this) // (**) - // becomes + // 变成了 longEar.__proto__.eat.call(this) - // that is + // 即等同于 rabbit.eat.call(this); ``` -2. Then in the line `(*)` of `rabbit.eat`, we'd like to pass the call even higher in the chain, but `this=longEar`, so `this.__proto__.eat` is again `rabbit.eat`! + +2. 之后在 `rabbit.eat` 的 `(*)` 行中,我们希望将函数调用在原型链上向更高层传递,但是因为 `this=longEar`,因此 `this.__proto__.eat` 又是 `rabbit.eat`! ```js - // inside rabbit.eat() we also have this = longEar + // 在 rabbit.eat() 中 this 依旧等于 longEar this.__proto__.eat.call(this) // (*) - // becomes + // 变成了 longEar.__proto__.eat.call(this) - // or (again) + // 再次等同于 rabbit.eat.call(this); ``` -3. ...So `rabbit.eat` calls itself in the endless loop, because it can't ascend any further. +3. ...所以 `rabbit.eat` 不停地循环调用自己,因此它无法进一步地往原型链的更高层调用 -The problem can't be solved by using `this` alone. +这个问题没法单独使用 `this` 来解决。 ### `[[HomeObject]]` -To provide the solution, JavaScript adds one more special internal property for functions: `[[HomeObject]]`. +为了提供解决方法,JavaScript 为函数额外添加了一个特殊的内部属性:`[[HomeObject]]`。 + +**当一个函数被定义为类或者对象方法时,它的 `[[HomeObject]]` 属性就成为那个对象** -When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object. +这实际上违反了 “解除绑定” 功能的想法,因为函数会记录他们的对象,并且 `[[HomeObject]]` 不能被改变,所以这个绑定是永久的。因此这是一个非常重要的语言变化。 -Then `super` uses it to resolve the parent prototype and its methods. +但是这种改变是安全的。`[[HomeObject]]` 只有使用 `super` 调用父类的方法是才会被使用。所以它不会破坏兼容性。 -Let's see how it works, first with plain objects: +让我们看看它是如何帮助 `super` 运行的 —— 我们再次使用普通对象: ```js run let animal = { name: "Animal", - eat() { // animal.eat.[[HomeObject]] == animal + eat() { // [[HomeObject]] == animal alert(`${this.name} eats.`); } }; @@ -431,7 +388,7 @@ let animal = { let rabbit = { __proto__: animal, name: "Rabbit", - eat() { // rabbit.eat.[[HomeObject]] == rabbit + eat() { // [[HomeObject]] == rabbit super.eat(); } }; @@ -439,108 +396,184 @@ let rabbit = { let longEar = { __proto__: rabbit, name: "Long Ear", - eat() { // longEar.eat.[[HomeObject]] == longEar + eat() { // [[HomeObject]] == longEar super.eat(); } }; *!* -// works correctly longEar.eat(); // Long Ear eats. */!* ``` -It works as intended, due to `[[HomeObject]]` mechanics. A method, such as `longEar.eat`, knows its `[[HomeObject]]` and takes the parent method from its prototype. Without any use of `this`. +每个方法都会在内部的 `[[HomeObject]]` 属性上标记它的对象。然后 `super` 利用它来解析父级原型。 -### Methods are not "free" +`[[HomeObject]]` 是为类和简单对象中定义的方法定义的。但是对于对象,方法必须按照给定的方式定义:使用 `method()`,而不是 `"method: function()"`。 -As we've known before, generally functions are "free", not bound to objects in JavaScript. So they can be copied between objects and called with another `this`. - -The very existance of `[[HomeObject]]` violates that principle, because methods remember their objects. `[[HomeObject]]` can't be changed, so this bond is forever. - -The only place in the language where `[[HomeObject]]` is used -- is `super`. So, if a method does not use `super`, then we can still consider it free and copy between objects. But with `super` things may go wrong. - -Here's the demo of a wrong `super` call: +在下面的例子中,使用非方法语法来进行对比。`[[HomeObject]]` 属性没有被设置,并且此时继承没有生效: ```js run let animal = { - sayHi() { - console.log(`I'm an animal`); + eat: function() { // 应该使用简短语法:eat() {...} + // ... } }; let rabbit = { __proto__: animal, - sayHi() { - super.sayHi(); - } -}; - -let plant = { - sayHi() { - console.log("I'm a plant"); + eat: function() { + super.eat(); } }; -let tree = { - __proto__: plant, *!* - sayHi: rabbit.sayHi // (*) +rabbit.eat(); // 调用 super 报错(因为没有 [[HomeObject]]) */!* -}; +``` -*!* -tree.sayHi(); // I'm an animal (?!?) -*/!* +## 静态方法和继承 + +`class` 语法也支持静态属性的继承。 + +例如: + +```js run +class Animal { + + constructor(name, speed) { + this.speed = speed; + this.name = name; + } + + run(speed = 0) { + this.speed += speed; + alert(`${this.name} runs with speed ${this.speed}.`); + } + + static compare(animalA, animalB) { + return animalA.speed - animalB.speed; + } + +} + +// 继承自 Animal +class Rabbit extends Animal { + hide() { + alert(`${this.name} hides!`); + } +} + +let rabbits = [ + new Rabbit("White Rabbit", 10), + new Rabbit("Black Rabbit", 5) +]; + +rabbits.sort(Rabbit.compare); + +rabbits[0].run(); // Black Rabbit runs with speed 5. ``` -A call to `tree.sayHi()` shows "I'm an animal". Definitevely wrong. -The reason is simple: -- In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication? -- So its `[[HomeObject]]` is `rabbit`, as it was created in `rabbit`. There's no way to change `[[HomeObject]]`. -- The code of `tree.sayHi()` has `super.sayHi()` inside. It goes up from `rabbit` and takes the method from `animal`. +现在我们可以假定调用 `Rabbit.compare` 时是调用了继承的 `Rabbit.compare`。 -![](super-homeobject-wrong.png) +它是如何工作的?正如你已经猜测的那样,继承也给 `Rabbit` 的 `[[Prototype]]` 添加一个引用指向 `Animal`。 -### Methods, not function properties +![](animal-rabbit-static.png) -`[[HomeObject]]` is defined for methods both in classes and in plain objects. But for objects, methods must be specified exactly as `method()`, not as `"method: function()"`. -The difference may be non-essential for us, but it's important for JavaScript. +所以,`Rabbit` 函数现在继承自 `Animal` 函数。`Animal` 函数因为没有继承自任何内容,它的 `[[Prototype]]` 指向 `Function.prototype`。 -In the example below a non-method syntax is used for comparison. `[[HomeObject]]` property is not set and the inheritance doesn't work: +现在,让我们来验证一下: ```js run -let animal = { - eat: function() { // should be the short syntax: eat() {...} - // ... +class Animal {} +class Rabbit extends Animal {} + +// 验证静态属性和方法 +alert(Rabbit.__proto__ === Animal); // true + +// 下一步是验证 Function.prototype +alert(Animal.__proto__ === Function.prototype); // true + +// 额外验证一下普通对象方法的原型链 +alert(Rabbit.prototype.__proto__ === Animal.prototype); +``` + +这样 `Rabbit` 就可以访问 `Animal` 的所有静态方法。 + +### 内置类没有静态方法继承 + +请注意,内置类没有这种静态 `[[Prototype]]` 的引用。例如, +`Object` 有 `Object.defineProperty`,`Object.keys` 等等的方法,但是 `Array`,`Date` 等等并不会继承他们。 + +这里有一张图来描述 `Date` 和 `Object` 的结构: + +![](object-date-inheritance.png) + +请注意,`Date` 和 `Object` 之间没有关联。`Object` 和 `Date` 都是独立存在的。`Date.prototype` 继承自 `Object.prototype`,但也仅此而已。 + +这种差异是由于历史原因而存在的:在 JavaScript 语言被创建时,并没有考虑过类语法和静态方法的继承 + +## 原生方法是可扩展的 + +Array,Map 等 JavaScript 内置的类也是可以扩展的。 + +例如,这里的 `PowerArray` 继承自原生的 `Array`: + +```js run +// 给 Array 增加一个新方法(可以做更多功能) +class PowerArray extends Array { + isEmpty() { + return this.length === 0; } -}; +} -let rabbit = { - __proto__: animal, - eat: function() { - super.eat(); +let arr = new PowerArray(1, 2, 5, 10, 50); +alert(arr.isEmpty()); // false + +let filteredArr = arr.filter(item => item >= 10); +alert(filteredArr); // 10, 50 +alert(filteredArr.isEmpty()); // false +``` + +有一件非常有趣的事情需要注意。像 `filter`,`map` 或者其他原生的方法,都会根据继承的类型返回新的对象。他们都是依赖 `constructor` 属性实现的这一功能。 + +在上面的例子中: + +```js +arr.constructor === PowerArray +``` + +所以当调用 `arr.filter()` 时,就像 `new PowerArray` 一样,他会在内部创建新的结果数组。并且我们可以继续链式调用它的方法。 + +更重要的是,我们可以定制这种行为。在这种情况下,如果存在静态的 getter `Symbol.species`,那么就会使用它的返回值作为构造函数。 + +举个例子,这里因为有 `Symbol.species`,像 `map`,`filter` 这样的内置方法将返回“普通”数组: + +```js run +class PowerArray extends Array { + isEmpty() { + return this.length === 0; } -}; *!* -rabbit.eat(); // Error calling super (because there's no [[HomeObject]]) + // 内置函数会使用它作为构造函数 + static get [Symbol.species]() { + return Array; + } */!* -``` +} -## Summary +let arr = new PowerArray(1, 2, 5, 10, 50); +alert(arr.isEmpty()); // false -1. To extend a class: `class Child extends Parent`: - - That means `Child.prototype.__proto__` will be `Parent.prototype`, so methods are inherited. -2. When overriding a constructor: - - We must call parent constructor as `super()` in `Child` constructor before using `this`. -3. When overriding another method: - - We can use `super.method()` in a `Child` method to call `Parent` method. -4. Internals: - - Methods remember their class/object in the internal `[[HomeObject]]` property. That's how `super` resolves parent methods. - - So it's not safe to copy a method with `super` from one object to another. +// filter 使用 arr.constructor[Symbol.species] 作为构造函数创建了新数组 +let filteredArr = arr.filter(item => item >= 10); + +*!* +// filteredArr 不是 PowerArray,而是一个 普通数组 +*/!* +alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function +``` -Also: -- Arrow functions don't have own `this` or `super`, so they transparently fit into the surrounding context. +在一些高级的场景中,我们可以使用它从结果值中剔除一些不需要的扩展功能。又或者可以进一步扩展它。 From 4243d8b4d08331779be8e6b12bc86ac18b628fe2 Mon Sep 17 00:00:00 2001 From: LeviDing Date: Wed, 12 Jun 2019 09:23:07 +0800 Subject: [PATCH 02/10] delete 1-js/09-classes/02-class-inheritance/a-zh --- .../02-class-inheritance/article-zh.md | 579 ------------------ 1 file changed, 579 deletions(-) delete mode 100644 1-js/09-classes/02-class-inheritance/article-zh.md diff --git a/1-js/09-classes/02-class-inheritance/article-zh.md b/1-js/09-classes/02-class-inheritance/article-zh.md deleted file mode 100644 index 8989dfc51e..0000000000 --- a/1-js/09-classes/02-class-inheritance/article-zh.md +++ /dev/null @@ -1,579 +0,0 @@ - -# 类继承和 super - -类可以继承另外一个类。这是一个非常棒的语法,在技术上是它基于原型继承实现的。 - -为了继承另外一个类,我们需要在括号 `{..}` 前指定 `"extends"` 和父类 - -这里我们写一个继承自 `Animal` 的 `Rabbit`: - -```js run -class Animal { - - constructor(name) { - this.speed = 0; - this.name = name; - } - - run(speed) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); - } - - stop() { - this.speed = 0; - alert(`${this.name} stopped.`); - } - -} - -*!* -// 从 Animal 继承 -class Rabbit extends Animal { - hide() { - alert(`${this.name} hides!`); - } -} -*/!* - -let rabbit = new Rabbit("White Rabbit"); - -rabbit.run(5); // 白色兔子会以速度 5 奔跑。 -rabbit.hide(); // 白色兔子藏了起来! -``` - -就如你期望的那样,也正如我们之前所见,`extends` 关键字实际上是给 `Rabbit.prototype` 添加了一个属性 `[[Prototype]]`,并且它会指向 `Animal.prototype`。 - -![](animal-rabbit-extends.png) - -所以,现在 `rabbit` 即可以访问它自己的方法,也可以访问 `Animal` 的方法。 - -````smart header="Any expression is allowed after `extends`" -类语法不仅允许在 `extends` 后指定一个类,也允许指定任何表达式。 - -举个例子,通过一个函数调用生成父类: - -```js run -function f(phrase) { - return class { - sayHi() { alert(phrase) } - } -} - -*!* -class User extends f("Hello") {} -*/!* - -new User().sayHi(); // Hello -``` -这里 `class User` 继承自 `f("Hello")` 的执行结果。 - -对于一些高级的编程模式来说这可能会很有用,比如我们可以使用函数来根据许多条件生成不同的类,并可以从中继承。 -```` - -## 重写一个方法 - -现在让我们继续前进并尝试重写一个方法。到目前为止,`Rabbit` 继承了 `Animal` 的 `stop` 方法,该方法设置了 `this.speed = 0`。 - -如果我们在 `Rabbit` 中定义了我们自己的 `stop` 方法,那么它将被用来代替 `Animal` 的 `stop` 方法: - -```js -class Rabbit extends Animal { - stop() { - // ...这将用于 rabbit.stop() - } -} -``` - -...但是通常来说,我们不希望完全替换父类的方法,而是希望基于它做一些调整或者扩展。我们在我们的方法中做一些事情,但是在它之前/之后或在执行过程中调用父类的方法。 - -为此,类提供了 `"super"` 关键字。 - -- 执行 `super.method(...)` 来调用一个父类的方法。 -- 执行 `super(...)` 调用父类的构造函数 (只能在子类的构造函数中执行)。 - -例如,让我们的兔子在停下时自动隐藏: - -```js run -class Animal { - - constructor(name) { - this.speed = 0; - this.name = name; - } - - run(speed) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); - } - - stop() { - this.speed = 0; - alert(`${this.name} stopped.`); - } - -} - -class Rabbit extends Animal { - hide() { - alert(`${this.name} hides!`); - } - -*!* - stop() { - super.stop(); // 调用父类的 stop 函数 - this.hide(); // 并且在那之后隐藏 - } -*/!* -} - -let rabbit = new Rabbit("White Rabbit"); - -rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.stop(); // White Rabbit stopped. White rabbit hides! -``` - -现在,`Rabbit` 有自己的 `stop` 函数,并且在执行过程中会调用父类的 `super.stop()`。 - -````smart header="Arrow functions have no `super`" -就像在箭头函数 那一章节所提到的,箭头函数没有 `super`。 - -如果被访问,它将从外部函数获取。举个例子: - -```js -class Rabbit extends Animal { - stop() { - setTimeout(() => super.stop(), 1000); // 1 秒后调用父类的 stop 函数 - } -} -``` - -箭头函数中的 `super` 与 `stop()` 中的相同,所以它能按预期工作。如果我们在这里指定的是一个“普通”函数,那么将会抛出一个错误: - -```js -// 未定义的 super -setTimeout(function() { super.stop() }, 1000); -``` -```` - - -## 重写构造函数 - -对于构造函数来说,重写则有点棘手。 - -到目前为止,`Rabbit` 还没有自己的 `constructor`。 - -根据[规范](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation),如果一个类继承了另一个类并且没有构造函数,那么将生成以下构造函数: - -```js -class Rabbit extends Animal { - // 为没有构造函数的继承类生成以下的构造函数 -*!* - constructor(...args) { - super(...args); - } -*/!* -} -``` - -我们可以看到,它调用了父类的 `constructor` 并传递了所有的参数。如果我们不写自己的构造函数,就会出现这种情况。 - -现在让我们给 `Rabbit` 增加一个自定义的构造函数。除了 `name` 它还会定义 `earLength`: - -```js run -class Animal { - constructor(name) { - this.speed = 0; - this.name = name; - } - // ... -} - -class Rabbit extends Animal { - -*!* - constructor(name, earLength) { - this.speed = 0; - this.name = name; - this.earLength = earLength; - } -*/!* - - // ... -} - -*!* -// 不生效! -let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. -*/!* -``` - -哎呦!我们得到一个报错。现在我们没法新建兔子了。是什么地方出错了? - -简短的解释下原因:继承类的构造函数必须调用 `super(...)`,并且一定要在使用 `this` 之前调用。 - -...但这是为什么呢?这里发生了什么?这个要求确实看起来很奇怪。 - -当然,本文会给出一个解释。让我们深入细节,这样你就可以真正的理解发生了什么。 - -在 JavaScript 中,“派生类的构造函数”与所有其他的构造函数之间存在区别。在派生类中,相应的构造函数会被标记为特殊的内部属性 `[[ConstructorKind]]:"derived"`。 - -不同点就在于: - -- 当一个普通构造函数执行时,它会创建一个空对象作为 `this` 并继续执行。 -- 但是当派生的构造函数执行时,它并不会做这件事。它期望父类的构造函数来完成这项工作。 - -因此,如果我们构建了我们自己的构造函数,我们必须调用 `super`,因为否则的话 `this` 指向的对象不会被创建,并且我们会收到一个报错。 - -为了让 `Rabbit` 可以运行,我们需要在使用 `this` 之前调用 `super()`,就像下面这样: - -```js run -class Animal { - - constructor(name) { - this.speed = 0; - this.name = name; - } - - // ... -} - -class Rabbit extends Animal { - - constructor(name, earLength) { -*!* - super(name); -*/!* - this.earLength = earLength; - } - - // ... -} - -*!* -// now fine -let rabbit = new Rabbit("White Rabbit", 10); -alert(rabbit.name); // White Rabbit -alert(rabbit.earLength); // 10 -*/!* -``` - - -## Super: 内部基于 [[HomeObject]] 实现 - -让我们再深入的去研究下 `super`。顺便说一句,我们会发现一些有趣的事情。 - -首先要说的是,从我们迄今为止学到的知识来看,`super` 是不可能运行的。 - -的确是这样,让我们问问自己,在技术上它是如何实现的?当一个对象方法运行时,它会将当前对象作为 `this`。如果之后我们调用 `super.method()`,那么如何检索 `method`?我们想当然地认为需要从当前对象的原型中获取 `method`。但是从技术上讲,我们(或者 JavaScript 的引擎)可以做到这一点吗? - -也许我们可以从 `this` 的 `[[Prototype]]` 上获得方法,就像 `this.__proto__.method`?不幸的是,这样是行不通的。 - -让我们尝试去这么做看看。简单起见,我们不使用类,只使用普通对象。 - -在这里,`rabbit.eat()` 会调用父对象的 `animal.eat()` 方法: - -```js run -let animal = { - name: "Animal", - eat() { - alert(`${this.name} eats.`); - } -}; - -let rabbit = { - __proto__: animal, - name: "Rabbit", - eat() { -*!* - // 这是 super.eat() 可能工作的原因 - this.__proto__.eat.call(this); // (*) -*/!* - } -}; - -rabbit.eat(); // Rabbit eats. -``` - -在 `(*)` 这一行,我们从 `animal` 的原型上获取 `eat`,并在当前对象的上下文中调用它。请注意,`.call(this)` 在这里非常重要,因为简单的调用 `this.__proto__.eat()` 将在原型的上下文中执行 `eat`,而非当前对象 - -在上述的代码中,它按照期望运行:我们获得了正确的 `alert`。 - -现在让我们在原型链上再添加一个额外的对象。我们将看到这件事是如何被打破的: - -```js run -let animal = { - name: "Animal", - eat() { - alert(`${this.name} eats.`); - } -}; - -let rabbit = { - __proto__: animal, - eat() { - // ...新建一个兔子并调用父类的方法 - this.__proto__.eat.call(this); // (*) - } -}; - -let longEar = { - __proto__: rabbit, - eat() { - // ...用长耳朵做一些事情,并调用父类(rabbit)的方法 - this.__proto__.eat.call(this); // (**) - } -}; - -*!* -longEar.eat(); // Error: Maximum call stack size exceeded -*/!* -``` - -代码无法再运行了!我们可以看到,在试图调用 `longEar.eat()` 时抛出了错误。 - -原因可能不那么明显,但是如果我们跟踪 `longEar.eat()` 的调用,就可以发现原因。在 `(*)` 和 `(**)` 这两行中,`this` 的值都是当前对象 `longEar`。这是至关重要的一点:所有的对象方法都将当前对象作为 `this`,而非原型或其他什么东西。 - -因此,在 `(*)` 和 `(**)` 这两行中,`this.__proto__` 的值是完全相同的,都是 `rabbit`。在这个无限循环中,他们都调用了 `rabbit.eat`,而不是在原型链上向上寻找方法。 - -这张图介绍了发生的情况: - -![](this-super-loop.png) - -1. 在 `longEar.eat()` 中,`(**)` 这一行调用 `rabbit.eat` 并且此时 `this=longEar`. - ```js - // 在 longEar.eat() 中 this 指向 longEar - this.__proto__.eat.call(this) // (**) - // 变成了 - longEar.__proto__.eat.call(this) - // 即等同于 - rabbit.eat.call(this); - ``` - -2. 之后在 `rabbit.eat` 的 `(*)` 行中,我们希望将函数调用在原型链上向更高层传递,但是因为 `this=longEar`,因此 `this.__proto__.eat` 又是 `rabbit.eat`! - - ```js - // 在 rabbit.eat() 中 this 依旧等于 longEar - this.__proto__.eat.call(this) // (*) - // 变成了 - longEar.__proto__.eat.call(this) - // 再次等同于 - rabbit.eat.call(this); - ``` - -3. ...所以 `rabbit.eat` 不停地循环调用自己,因此它无法进一步地往原型链的更高层调用 - -这个问题没法单独使用 `this` 来解决。 - -### `[[HomeObject]]` - -为了提供解决方法,JavaScript 为函数额外添加了一个特殊的内部属性:`[[HomeObject]]`。 - -**当一个函数被定义为类或者对象方法时,它的 `[[HomeObject]]` 属性就成为那个对象** - -这实际上违反了 “解除绑定” 功能的想法,因为函数会记录他们的对象,并且 `[[HomeObject]]` 不能被改变,所以这个绑定是永久的。因此这是一个非常重要的语言变化。 - -但是这种改变是安全的。`[[HomeObject]]` 只有使用 `super` 调用父类的方法是才会被使用。所以它不会破坏兼容性。 - -让我们看看它是如何帮助 `super` 运行的 —— 我们再次使用普通对象: - -```js run -let animal = { - name: "Animal", - eat() { // [[HomeObject]] == animal - alert(`${this.name} eats.`); - } -}; - -let rabbit = { - __proto__: animal, - name: "Rabbit", - eat() { // [[HomeObject]] == rabbit - super.eat(); - } -}; - -let longEar = { - __proto__: rabbit, - name: "Long Ear", - eat() { // [[HomeObject]] == longEar - super.eat(); - } -}; - -*!* -longEar.eat(); // Long Ear eats. -*/!* -``` - -每个方法都会在内部的 `[[HomeObject]]` 属性上标记它的对象。然后 `super` 利用它来解析父级原型。 - -`[[HomeObject]]` 是为类和简单对象中定义的方法定义的。但是对于对象,方法必须按照给定的方式定义:使用 `method()`,而不是 `"method: function()"`。 - -在下面的例子中,使用非方法语法来进行对比。`[[HomeObject]]` 属性没有被设置,并且此时继承没有生效: - -```js run -let animal = { - eat: function() { // 应该使用简短语法:eat() {...} - // ... - } -}; - -let rabbit = { - __proto__: animal, - eat: function() { - super.eat(); - } -}; - -*!* -rabbit.eat(); // 调用 super 报错(因为没有 [[HomeObject]]) -*/!* -``` - -## 静态方法和继承 - -`class` 语法也支持静态属性的继承。 - -例如: - -```js run -class Animal { - - constructor(name, speed) { - this.speed = speed; - this.name = name; - } - - run(speed = 0) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); - } - - static compare(animalA, animalB) { - return animalA.speed - animalB.speed; - } - -} - -// 继承自 Animal -class Rabbit extends Animal { - hide() { - alert(`${this.name} hides!`); - } -} - -let rabbits = [ - new Rabbit("White Rabbit", 10), - new Rabbit("Black Rabbit", 5) -]; - -rabbits.sort(Rabbit.compare); - -rabbits[0].run(); // Black Rabbit runs with speed 5. -``` - - -现在我们可以假定调用 `Rabbit.compare` 时是调用了继承的 `Rabbit.compare`。 - -它是如何工作的?正如你已经猜测的那样,继承也给 `Rabbit` 的 `[[Prototype]]` 添加一个引用指向 `Animal`。 - -![](animal-rabbit-static.png) - - -所以,`Rabbit` 函数现在继承自 `Animal` 函数。`Animal` 函数因为没有继承自任何内容,它的 `[[Prototype]]` 指向 `Function.prototype`。 - -现在,让我们来验证一下: - -```js run -class Animal {} -class Rabbit extends Animal {} - -// 验证静态属性和方法 -alert(Rabbit.__proto__ === Animal); // true - -// 下一步是验证 Function.prototype -alert(Animal.__proto__ === Function.prototype); // true - -// 额外验证一下普通对象方法的原型链 -alert(Rabbit.prototype.__proto__ === Animal.prototype); -``` - -这样 `Rabbit` 就可以访问 `Animal` 的所有静态方法。 - -### 内置类没有静态方法继承 - -请注意,内置类没有这种静态 `[[Prototype]]` 的引用。例如, -`Object` 有 `Object.defineProperty`,`Object.keys` 等等的方法,但是 `Array`,`Date` 等等并不会继承他们。 - -这里有一张图来描述 `Date` 和 `Object` 的结构: - -![](object-date-inheritance.png) - -请注意,`Date` 和 `Object` 之间没有关联。`Object` 和 `Date` 都是独立存在的。`Date.prototype` 继承自 `Object.prototype`,但也仅此而已。 - -这种差异是由于历史原因而存在的:在 JavaScript 语言被创建时,并没有考虑过类语法和静态方法的继承 - -## 原生方法是可扩展的 - -Array,Map 等 JavaScript 内置的类也是可以扩展的。 - -例如,这里的 `PowerArray` 继承自原生的 `Array`: - -```js run -// 给 Array 增加一个新方法(可以做更多功能) -class PowerArray extends Array { - isEmpty() { - return this.length === 0; - } -} - -let arr = new PowerArray(1, 2, 5, 10, 50); -alert(arr.isEmpty()); // false - -let filteredArr = arr.filter(item => item >= 10); -alert(filteredArr); // 10, 50 -alert(filteredArr.isEmpty()); // false -``` - -有一件非常有趣的事情需要注意。像 `filter`,`map` 或者其他原生的方法,都会根据继承的类型返回新的对象。他们都是依赖 `constructor` 属性实现的这一功能。 - -在上面的例子中: - -```js -arr.constructor === PowerArray -``` - -所以当调用 `arr.filter()` 时,就像 `new PowerArray` 一样,他会在内部创建新的结果数组。并且我们可以继续链式调用它的方法。 - -更重要的是,我们可以定制这种行为。在这种情况下,如果存在静态的 getter `Symbol.species`,那么就会使用它的返回值作为构造函数。 - -举个例子,这里因为有 `Symbol.species`,像 `map`,`filter` 这样的内置方法将返回“普通”数组: - -```js run -class PowerArray extends Array { - isEmpty() { - return this.length === 0; - } - -*!* - // 内置函数会使用它作为构造函数 - static get [Symbol.species]() { - return Array; - } -*/!* -} - -let arr = new PowerArray(1, 2, 5, 10, 50); -alert(arr.isEmpty()); // false - -// filter 使用 arr.constructor[Symbol.species] 作为构造函数创建了新数组 -let filteredArr = arr.filter(item => item >= 10); - -*!* -// filteredArr 不是 PowerArray,而是一个 普通数组 -*/!* -alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function -``` - -在一些高级的场景中,我们可以使用它从结果值中剔除一些不需要的扩展功能。又或者可以进一步扩展它。 From 6a178280ff7d05a42dfb8b3e00b64a031fd5ffd6 Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Thu, 13 Jun 2019 17:36:19 +0800 Subject: [PATCH 03/10] Finished translation --- .../02-class-inheritance/article.md | 371 ++++++++---------- 1 file changed, 169 insertions(+), 202 deletions(-) diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 8989dfc51e..12bac07f65 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -1,57 +1,103 @@ -# 类继承和 super +# 类继承 -类可以继承另外一个类。这是一个非常棒的语法,在技术上是它基于原型继承实现的。 +假设我们有两个类: -为了继承另外一个类,我们需要在括号 `{..}` 前指定 `"extends"` 和父类 +`Animal`: + +```js +class Animal { + constructor(name) { + this.speed = 0; + this.name = name; + } + run(speed) { + this.speed += speed; + alert(`${this.name} runs with speed ${this.speed}.`); + } + stop() { + this.speed = 0; + alert(`${this.name} stopped.`); + } +} + +let animal = new Animal("My animal"); +``` + +![](rabbit-animal-independent-animal.png) + + +...和 `Rabbit`: + +```js +class Rabbit { + constructor(name) { + this.name = name; + } + hide() { + alert(`${this.name} hides!`); + } +} + +let rabbit = new Rabbit("My rabbit"); +``` + +![](rabbit-animal-independent-rabbit.png) -这里我们写一个继承自 `Animal` 的 `Rabbit`: + +现在,它们是完全独立的。 + +但是,我们想要 `Rabbit` 继承自 `Animal`。换句话说,rabbits 应该基于 animals,具有访问 `Animal` 的权限,并使用自己的方法扩展它们。 + +要继承自另一个类,我们需要在 `{..}` 前指定 `“extends”` 和父类。 + +在这里,`Rabbit` 继承自 `Animal`: ```js run class Animal { - constructor(name) { this.speed = 0; this.name = name; } - run(speed) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } - stop() { this.speed = 0; alert(`${this.name} stopped.`); } - } +// 通过指定“extends Animal”让 Rabbit 继承自 Animal *!* -// 从 Animal 继承 class Rabbit extends Animal { +*/!* hide() { alert(`${this.name} hides!`); } } -*/!* let rabbit = new Rabbit("White Rabbit"); -rabbit.run(5); // 白色兔子会以速度 5 奔跑。 -rabbit.hide(); // 白色兔子藏了起来! +rabbit.run(5); // White Rabbit runs with speed 5. +rabbit.hide(); // White Rabbit hides! ``` -就如你期望的那样,也正如我们之前所见,`extends` 关键字实际上是给 `Rabbit.prototype` 添加了一个属性 `[[Prototype]]`,并且它会指向 `Animal.prototype`。 +现在 `Rabbit` 代码变简洁了一点,因为它默认以 `Animal` 作为其构造函数,同时它仍然能良好“运行”,就像 animals 一样。 + +在其内部,`extends` 关键字将 `[[Prototype]]` 引用从 `Rabbit.prototype` 添加到 `Animal.prototype`: ![](animal-rabbit-extends.png) -所以,现在 `rabbit` 即可以访问它自己的方法,也可以访问 `Animal` 的方法。 +因此,如果在 `Rabbit.prototype` 中没有找到某个方法,JavaScript 将会从 `Animal.prototype` 中获取它。 + +我们可以回忆一下这一章 ,JavaScript 对内置对象使用相同的类型继承。例如, `Date.prototype.[[Prototype]]` 是 `Object.prototype`,所以 dates 有通用的对象方法。 -````smart header="Any expression is allowed after `extends`" -类语法不仅允许在 `extends` 后指定一个类,也允许指定任何表达式。 +````smart header="`extends` 后允许任何表达式" +类语法不仅可以指定一个类,还可以指定 `extends` 之后的任何表达式。 -举个例子,通过一个函数调用生成父类: +例如,一个生成父类的函数调用: ```js run function f(phrase) { @@ -66,16 +112,16 @@ class User extends f("Hello") {} new User().sayHi(); // Hello ``` -这里 `class User` 继承自 `f("Hello")` 的执行结果。 +这里 `class User` 继承自 `f("Hello")` 的结果 -对于一些高级的编程模式来说这可能会很有用,比如我们可以使用函数来根据许多条件生成不同的类,并可以从中继承。 +当我们使用函数根据许多条件生成类并且可以从它们继承时,这对于高级编程模式来说可能很有用。 ```` -## 重写一个方法 +## 重写方法 -现在让我们继续前进并尝试重写一个方法。到目前为止,`Rabbit` 继承了 `Animal` 的 `stop` 方法,该方法设置了 `this.speed = 0`。 +现在,让我们继续前行并尝试重写一个方法。到目前为止,`Rabbit` 继承了 `Animal` 中的 `stop` 方法,该方法设置了 `this.speed = 0`。 -如果我们在 `Rabbit` 中定义了我们自己的 `stop` 方法,那么它将被用来代替 `Animal` 的 `stop` 方法: +如果我们在 `Rabbit` 中定义了我们自己的 `stop` 方法,那么它将被用来代替 `Animal` 中的 `stop`: ```js class Rabbit extends Animal { @@ -85,14 +131,15 @@ class Rabbit extends Animal { } ``` -...但是通常来说,我们不希望完全替换父类的方法,而是希望基于它做一些调整或者扩展。我们在我们的方法中做一些事情,但是在它之前/之后或在执行过程中调用父类的方法。 + +...但是通常来说,我们不希望完全替换父类的方法,而是希望基于它做一些调整或者功能性的扩展。我们在我们的方法中做一些事情,但是在它之前/之后或在执行过程中调用父类方法。 为此,类提供了 `"super"` 关键字。 -- 执行 `super.method(...)` 来调用一个父类的方法。 -- 执行 `super(...)` 调用父类的构造函数 (只能在子类的构造函数中执行)。 +- 执行 `super.method(...)` 调用父类方法。 +- 执行 `super(...)` 调用父类构造函数(只能在子类的构造函数中运行)。 -例如,让我们的兔子在停下时自动隐藏: +例如,让我们的兔子在停下来的时候自动隐藏: ```js run class Animal { @@ -122,7 +169,7 @@ class Rabbit extends Animal { *!* stop() { super.stop(); // 调用父类的 stop 函数 - this.hide(); // 并且在那之后隐藏 + this.hide(); // 然后隐藏 } */!* } @@ -133,25 +180,24 @@ rabbit.run(5); // White Rabbit runs with speed 5. rabbit.stop(); // White Rabbit stopped. White rabbit hides! ``` -现在,`Rabbit` 有自己的 `stop` 函数,并且在执行过程中会调用父类的 `super.stop()`。 - -````smart header="Arrow functions have no `super`" -就像在箭头函数 那一章节所提到的,箭头函数没有 `super`。 +现在 `Rabbit` 拥有自己的 `stop` 方法,并且在执行中会调用父类的 `super.stop()`。 -如果被访问,它将从外部函数获取。举个例子: +````smart header="箭头函数没有 `super`" +正如我们在 章节中所提到的,箭头函数没有 `super`。 +如果被访问,它会从外部函数获取。例如: ```js class Rabbit extends Animal { stop() { - setTimeout(() => super.stop(), 1000); // 1 秒后调用父类的 stop 函数 + setTimeout(() => super.stop(), 1000); // 1 秒后调用父类 stop 方法 } } ``` -箭头函数中的 `super` 与 `stop()` 中的相同,所以它能按预期工作。如果我们在这里指定的是一个“普通”函数,那么将会抛出一个错误: +箭头函数中的 `super` 与 `stop()` 是相同的,所以它能按预期工作。如果我们在这里指定一个“普通”函数,那么将会抛出错误: ```js -// 未定义的 super +// Unexpected super setTimeout(function() { super.stop() }, 1000); ``` ```` @@ -163,7 +209,7 @@ setTimeout(function() { super.stop() }, 1000); 到目前为止,`Rabbit` 还没有自己的 `constructor`。 -根据[规范](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation),如果一个类继承了另一个类并且没有构造函数,那么将生成以下构造函数: +根据 [规范](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation),如果一个类继承了另一个类并且没有 `constructor`,那么将生成以下 “空” `constructor`: ```js class Rabbit extends Animal { @@ -176,9 +222,9 @@ class Rabbit extends Animal { } ``` -我们可以看到,它调用了父类的 `constructor` 并传递了所有的参数。如果我们不写自己的构造函数,就会出现这种情况。 +我们可以看到,它调用了父类的 `constructor`,并传递了所有的参数。如果我们不写自己的构造函数,就会出现这种情况。 -现在让我们给 `Rabbit` 增加一个自定义的构造函数。除了 `name` 它还会定义 `earLength`: +现在,我们给 `Rabbit` 添加一个自定义的构造函数。除了 `name` 它还会定义 `earLength`。 ```js run class Animal { @@ -204,26 +250,26 @@ class Rabbit extends Animal { *!* // 不生效! -let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. +let rabbit = new Rabbit("White Rabbit", 10); // Error: this 未定义 */!* ``` 哎呦!我们得到一个报错。现在我们没法新建兔子了。是什么地方出错了? -简短的解释下原因:继承类的构造函数必须调用 `super(...)`,并且一定要在使用 `this` 之前调用。 +简短的解释下原因:继承类的构造函数必须调用 `super(...)`,并且 (!) 一定要在使用 this 之前调用。 ...但这是为什么呢?这里发生了什么?这个要求确实看起来很奇怪。 当然,本文会给出一个解释。让我们深入细节,这样你就可以真正的理解发生了什么。 -在 JavaScript 中,“派生类的构造函数”与所有其他的构造函数之间存在区别。在派生类中,相应的构造函数会被标记为特殊的内部属性 `[[ConstructorKind]]:"derived"`。 +在 JavaScript 中,“继承类的构造函数”与所有其他的构造函数之间存在区别。在继承类中,相应的构造函数会被标记为特殊的内部属性 `[[ConstructorKind]]:"derived"`。 -不同点就在于: +不同点就在于: - 当一个普通构造函数执行时,它会创建一个空对象作为 `this` 并继续执行。 -- 但是当派生的构造函数执行时,它并不会做这件事。它期望父类的构造函数来完成这项工作。 +- 但是当继承的构造函数执行时,它并不会做这件事。它期望父类的构造函数来完成这项工作。 -因此,如果我们构建了我们自己的构造函数,我们必须调用 `super`,因为否则的话 `this` 指向的对象不会被创建,并且我们会收到一个报错。 +因此,如果我们构建了我们自己的构造函数,我们必须调用 `super`,因为如果不这样的话 `this` 指向的对象不会被创建。并且我们会收到一个报错。 为了让 `Rabbit` 可以运行,我们需要在使用 `this` 之前调用 `super()`,就像下面这样: @@ -251,7 +297,7 @@ class Rabbit extends Animal { } *!* -// now fine +// 现在可以了 let rabbit = new Rabbit("White Rabbit", 10); alert(rabbit.name); // White Rabbit alert(rabbit.earLength); // 10 @@ -259,19 +305,19 @@ alert(rabbit.earLength); // 10 ``` -## Super: 内部基于 [[HomeObject]] 实现 +## Super:内部基于 [[HomeObject]] 实现 让我们再深入的去研究下 `super`。顺便说一句,我们会发现一些有趣的事情。 首先要说的是,从我们迄今为止学到的知识来看,`super` 是不可能运行的。 -的确是这样,让我们问问自己,在技术上它是如何实现的?当一个对象方法运行时,它会将当前对象作为 `this`。如果之后我们调用 `super.method()`,那么如何检索 `method`?我们想当然地认为需要从当前对象的原型中获取 `method`。但是从技术上讲,我们(或者 JavaScript 的引擎)可以做到这一点吗? +的确是这样,让我们问问自己,在技术上它是如何实现的?当一个对象方法运行时,它会将当前对象作为 `this`。如果之后我们调用 `super.method()`,它需要从当前对象的原型中调用 `method`。 -也许我们可以从 `this` 的 `[[Prototype]]` 上获得方法,就像 `this.__proto__.method`?不幸的是,这样是行不通的。 +任务看起来是挺容易的,但其实并不简单。引擎知道当前对象的 `this`,因此它可以获取父 `method` 作为 `this.__proto__.method`。不幸的是,这个“天真”的解决方法是行不通的。 -让我们尝试去这么做看看。简单起见,我们不使用类,只使用普通对象。 +让我们来说明一下这个问题。没有类,为简单起见,使用普通对象。 -在这里,`rabbit.eat()` 会调用父对象的 `animal.eat()` 方法: +在下面的例子中是 `rabbit.__proto__ = animal`。现在让我们尝试一下:在 `rabbit.eat()` 我们将会通过 `this.__proto__` 调用 `animal.eat()`: ```js run let animal = { @@ -286,7 +332,7 @@ let rabbit = { name: "Rabbit", eat() { *!* - // 这是 super.eat() 可能工作的原因 + // 这就是 super.eat() 可能运行的原因 this.__proto__.eat.call(this); // (*) */!* } @@ -295,7 +341,7 @@ let rabbit = { rabbit.eat(); // Rabbit eats. ``` -在 `(*)` 这一行,我们从 `animal` 的原型上获取 `eat`,并在当前对象的上下文中调用它。请注意,`.call(this)` 在这里非常重要,因为简单的调用 `this.__proto__.eat()` 将在原型的上下文中执行 `eat`,而非当前对象 +在 (\*) 这一行,我们从原型(`animal`)上获取 `eat`,并在当前对象的上下文中调用它。请注意,`.call(this)` 在这里非常重要,因为简单的调用 `this.__proto__.eat()` 将在原型的上下文中执行 `eat`,而非当前对象。 在上述的代码中,它按照期望运行:我们获得了正确的 `alert`。 @@ -312,7 +358,7 @@ let animal = { let rabbit = { __proto__: animal, eat() { - // ...新建一个兔子并调用父类的方法 + // ...围绕 rabbit-style 和 调用父类(animal)方法 this.__proto__.eat.call(this); // (*) } }; @@ -332,15 +378,15 @@ longEar.eat(); // Error: Maximum call stack size exceeded 代码无法再运行了!我们可以看到,在试图调用 `longEar.eat()` 时抛出了错误。 -原因可能不那么明显,但是如果我们跟踪 `longEar.eat()` 的调用,就可以发现原因。在 `(*)` 和 `(**)` 这两行中,`this` 的值都是当前对象 `longEar`。这是至关重要的一点:所有的对象方法都将当前对象作为 `this`,而非原型或其他什么东西。 +原因可能不那么明显,但是如果我们跟踪 `longEar.eat()` 的调用,就可以发现原因。在 (\*) 和 (\*\*) 这两行中,`this` 的值都是当前对象(`longEar`)。这是至关重要的一点:所有的对象方法都将当前对象作为 `this`,而非原型或其他什么东西。 -因此,在 `(*)` 和 `(**)` 这两行中,`this.__proto__` 的值是完全相同的,都是 `rabbit`。在这个无限循环中,他们都调用了 `rabbit.eat`,而不是在原型链上向上寻找方法。 +因此,在 (\*) 和 (\*\*) 这两行中,`this.__proto__` 的值是完全相同的:都是 `rabbit`。在这个无限循环中,他们都调用了 `rabbit.eat`,而不是在原型链上向上寻找方法。 这张图介绍了发生的情况: ![](this-super-loop.png) -1. 在 `longEar.eat()` 中,`(**)` 这一行调用 `rabbit.eat` 并且此时 `this=longEar`. +1. 在 `longEar.eat()` 中,(\*\*) 这一行调用 `rabbit.eat` 并且此时 `this=longEar`。 ```js // 在 longEar.eat() 中 this 指向 longEar this.__proto__.eat.call(this) // (**) @@ -349,8 +395,7 @@ longEar.eat(); // Error: Maximum call stack size exceeded // 即等同于 rabbit.eat.call(this); ``` - -2. 之后在 `rabbit.eat` 的 `(*)` 行中,我们希望将函数调用在原型链上向更高层传递,但是因为 `this=longEar`,因此 `this.__proto__.eat` 又是 `rabbit.eat`! +2. 之后在 `rabbit.eat` 的 (\*) 行中,我们希望将函数调用在原型链上向更高层传递,但是因为 `this=longEar`,因此 `this.__proto__.eat` 又是 `rabbit.eat`! ```js // 在 rabbit.eat() 中 this 依旧等于 longEar @@ -369,18 +414,16 @@ longEar.eat(); // Error: Maximum call stack size exceeded 为了提供解决方法,JavaScript 为函数额外添加了一个特殊的内部属性:`[[HomeObject]]`。 -**当一个函数被定义为类或者对象方法时,它的 `[[HomeObject]]` 属性就成为那个对象** - -这实际上违反了 “解除绑定” 功能的想法,因为函数会记录他们的对象,并且 `[[HomeObject]]` 不能被改变,所以这个绑定是永久的。因此这是一个非常重要的语言变化。 +当一个函数被定义为类或者对象方法时,它的 `[[HomeObject]]` 属性就成为那个对象。 -但是这种改变是安全的。`[[HomeObject]]` 只有使用 `super` 调用父类的方法是才会被使用。所以它不会破坏兼容性。 +然后 `super` 使用它来解析父类原型和它自己的方法。 -让我们看看它是如何帮助 `super` 运行的 —— 我们再次使用普通对象: +让我们看看它是怎么工作的,首先,对于普通对象: ```js run let animal = { name: "Animal", - eat() { // [[HomeObject]] == animal + eat() { // animal.eat.[[HomeObject]] == animal alert(`${this.name} eats.`); } }; @@ -388,7 +431,7 @@ let animal = { let rabbit = { __proto__: animal, name: "Rabbit", - eat() { // [[HomeObject]] == rabbit + eat() { // rabbit.eat.[[HomeObject]] == rabbit super.eat(); } }; @@ -396,184 +439,108 @@ let rabbit = { let longEar = { __proto__: rabbit, name: "Long Ear", - eat() { // [[HomeObject]] == longEar + eat() { // longEar.eat.[[HomeObject]] == longEar super.eat(); } }; *!* +// 正常运行 longEar.eat(); // Long Ear eats. */!* ``` -每个方法都会在内部的 `[[HomeObject]]` 属性上标记它的对象。然后 `super` 利用它来解析父级原型。 +它按照预期运行,基于 `[[HomeObject]]` 技巧。像 `longEar.eat` 这样的方法,知道 `[[HomeObject]]`,并且从它的原型中获取父类方法。并没有使用 `this`。 -`[[HomeObject]]` 是为类和简单对象中定义的方法定义的。但是对于对象,方法必须按照给定的方式定义:使用 `method()`,而不是 `"method: function()"`。 +### 方法并不是 “自由” 的 -在下面的例子中,使用非方法语法来进行对比。`[[HomeObject]]` 属性没有被设置,并且此时继承没有生效: +在前面我们已经知道,通常函数都是 “自由” 的,并没有绑定到 JavaScript。正因如此,它们可以在对象之间复制,并且用另外一个 `this` 调用它。 + +`[[HomeObject]]` 的存在违反了这个原则,因为方法记住了它们的对象。`[[HomeObject]]` 不能被修改,所以这个绑定时永久的。 + +在 JavaScript 语言中 `[[HomeObject]]` 仅被用于 `super`。所以,如果一个方法不使用 `super`,那么我们仍然可以视它为自由的并且可在对象之间复制。但是在 `super` 中可能出错。 + +下面是错误的 `super` 调用示例: ```js run let animal = { - eat: function() { // 应该使用简短语法:eat() {...} - // ... + sayHi() { + console.log(`I'm an animal`); } }; let rabbit = { __proto__: animal, - eat: function() { - super.eat(); + sayHi() { + super.sayHi(); } }; -*!* -rabbit.eat(); // 调用 super 报错(因为没有 [[HomeObject]]) -*/!* -``` - -## 静态方法和继承 - -`class` 语法也支持静态属性的继承。 - -例如: - -```js run -class Animal { - - constructor(name, speed) { - this.speed = speed; - this.name = name; - } - - run(speed = 0) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); +let plant = { + sayHi() { + console.log("I'm a plant"); } +}; - static compare(animalA, animalB) { - return animalA.speed - animalB.speed; - } - -} - -// 继承自 Animal -class Rabbit extends Animal { - hide() { - alert(`${this.name} hides!`); - } -} - -let rabbits = [ - new Rabbit("White Rabbit", 10), - new Rabbit("Black Rabbit", 5) -]; - -rabbits.sort(Rabbit.compare); - -rabbits[0].run(); // Black Rabbit runs with speed 5. -``` - - -现在我们可以假定调用 `Rabbit.compare` 时是调用了继承的 `Rabbit.compare`。 - -它是如何工作的?正如你已经猜测的那样,继承也给 `Rabbit` 的 `[[Prototype]]` 添加一个引用指向 `Animal`。 - -![](animal-rabbit-static.png) - - -所以,`Rabbit` 函数现在继承自 `Animal` 函数。`Animal` 函数因为没有继承自任何内容,它的 `[[Prototype]]` 指向 `Function.prototype`。 - -现在,让我们来验证一下: - -```js run -class Animal {} -class Rabbit extends Animal {} - -// 验证静态属性和方法 -alert(Rabbit.__proto__ === Animal); // true - -// 下一步是验证 Function.prototype -alert(Animal.__proto__ === Function.prototype); // true +let tree = { + __proto__: plant, +*!* + sayHi: rabbit.sayHi // (*) +*/!* +}; -// 额外验证一下普通对象方法的原型链 -alert(Rabbit.prototype.__proto__ === Animal.prototype); +*!* +tree.sayHi(); // I'm an animal (?!?) +*/!* ``` -这样 `Rabbit` 就可以访问 `Animal` 的所有静态方法。 - -### 内置类没有静态方法继承 - -请注意,内置类没有这种静态 `[[Prototype]]` 的引用。例如, -`Object` 有 `Object.defineProperty`,`Object.keys` 等等的方法,但是 `Array`,`Date` 等等并不会继承他们。 - -这里有一张图来描述 `Date` 和 `Object` 的结构: +调用 `tree.sayHi()` 显示 "I'm an animal"。绝对是错误的。 -![](object-date-inheritance.png) +原因很简单: +- 在 (\*) 行,`tree.sayHi` 方法从 `rabbit` 复制而来。也许我们只是想避免重复代码? +- 所以它的 `[[HomeObject]]` 是 `rabbit`,因为他是在 `rabbit` 中创建的。没有办法修改 `[[HomeObject]]`。 +- `tree.sayHi()` 内具有 `super.sayHi()`。它从 `rabbit` 中上溯,然后从 `animal` 中获取方法。 -请注意,`Date` 和 `Object` 之间没有关联。`Object` 和 `Date` 都是独立存在的。`Date.prototype` 继承自 `Object.prototype`,但也仅此而已。 +![](super-homeobject-wrong.png) -这种差异是由于历史原因而存在的:在 JavaScript 语言被创建时,并没有考虑过类语法和静态方法的继承 +### 方法,不是函数属性 -## 原生方法是可扩展的 +`[[HomeObject]]` 是为类和普通对象中的方法定义的。但是对于对象来说,方法必须确切指定为 `method()`,而不是 `"method: function()"`。 -Array,Map 等 JavaScript 内置的类也是可以扩展的。 +这个差别对我们来说可能不重要,但是对 JavaScript 来说却是非常重要的。 -例如,这里的 `PowerArray` 继承自原生的 `Array`: +下面的例子中,使用非方法(non-method)语句进行比较。`[[HomeObject]]` 属性未设置,并且继承不起作用: ```js run -// 给 Array 增加一个新方法(可以做更多功能) -class PowerArray extends Array { - isEmpty() { - return this.length === 0; +let animal = { + eat: function() { // 可以用短语法来书写:eat() {...} + // ... } -} - -let arr = new PowerArray(1, 2, 5, 10, 50); -alert(arr.isEmpty()); // false - -let filteredArr = arr.filter(item => item >= 10); -alert(filteredArr); // 10, 50 -alert(filteredArr.isEmpty()); // false -``` - -有一件非常有趣的事情需要注意。像 `filter`,`map` 或者其他原生的方法,都会根据继承的类型返回新的对象。他们都是依赖 `constructor` 属性实现的这一功能。 - -在上面的例子中: - -```js -arr.constructor === PowerArray -``` - -所以当调用 `arr.filter()` 时,就像 `new PowerArray` 一样,他会在内部创建新的结果数组。并且我们可以继续链式调用它的方法。 - -更重要的是,我们可以定制这种行为。在这种情况下,如果存在静态的 getter `Symbol.species`,那么就会使用它的返回值作为构造函数。 - -举个例子,这里因为有 `Symbol.species`,像 `map`,`filter` 这样的内置方法将返回“普通”数组: +}; -```js run -class PowerArray extends Array { - isEmpty() { - return this.length === 0; +let rabbit = { + __proto__: animal, + eat: function() { + super.eat(); } +}; *!* - // 内置函数会使用它作为构造函数 - static get [Symbol.species]() { - return Array; - } +rabbit.eat(); // 错误调用 super(因为这里并没有 [[HomeObject]]) */!* -} - -let arr = new PowerArray(1, 2, 5, 10, 50); -alert(arr.isEmpty()); // false +``` -// filter 使用 arr.constructor[Symbol.species] 作为构造函数创建了新数组 -let filteredArr = arr.filter(item => item >= 10); +## 总结 -*!* -// filteredArr 不是 PowerArray,而是一个 普通数组 -*/!* -alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function -``` +1. 扩展类:`class Child extends Parent`: + - 这就意味着 `Child.prototype.__proto__` 将是 `Parent.prototype`,所以方法被继承。 +2. 重写构造函数: + - 在使用 `this` 之前,我们必须在 `Child` 构造函数中将父构造函数调用为 `super()`。 +3. 重写方法: + - 我们可以在 `Child` 方法中使用 `super.method()` 来调用 `Parent` 方法。 +4. 内部工作: + - 方法在内部 `[[HomeObject]]` 属性中记住它们的类/对象。这就是 `super` 如何解析父类方法的。 + - 因此,将一个带有 `super` 的方法从一个对象复制到另一个对象是不安全的。 -在一些高级的场景中,我们可以使用它从结果值中剔除一些不需要的扩展功能。又或者可以进一步扩展它。 +补充: +- 箭头函数没有自己的 `this` 或 `super`,所以它们能很好的适应周围的环境。 From 4525a7fe9152d6f7ff441da0dbb66bd2e30aa56c Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Thu, 13 Jun 2019 17:48:48 +0800 Subject: [PATCH 04/10] Update 1-js/09-classes/02-class-inheritance/article.md --- 1-js/09-classes/02-class-inheritance/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 12bac07f65..3acb036d85 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -92,7 +92,7 @@ rabbit.hide(); // White Rabbit hides! 因此,如果在 `Rabbit.prototype` 中没有找到某个方法,JavaScript 将会从 `Animal.prototype` 中获取它。 -我们可以回忆一下这一章 ,JavaScript 对内置对象使用相同的类型继承。例如, `Date.prototype.[[Prototype]]` 是 `Object.prototype`,所以 dates 有通用的对象方法。 +我们可以回忆一下这一章 ,JavaScript 对内置对象使用相同的类型继承。例如,`Date.prototype.[[Prototype]]` 是 `Object.prototype`,所以 dates 有通用的对象方法。 ````smart header="`extends` 后允许任何表达式" 类语法不仅可以指定一个类,还可以指定 `extends` 之后的任何表达式。 From 54a7e2d55a275c82b10f27765812ecabc1c3a703 Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Thu, 13 Jun 2019 17:49:06 +0800 Subject: [PATCH 05/10] Update 1-js/09-classes/02-class-inheritance/article.md --- 1-js/09-classes/02-class-inheritance/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 3acb036d85..0f9362f1de 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -250,7 +250,7 @@ class Rabbit extends Animal { *!* // 不生效! -let rabbit = new Rabbit("White Rabbit", 10); // Error: this 未定义 +let rabbit = new Rabbit("White Rabbit", 10); // Error: Error: this is not defined. */!* ``` From 4e7efab472e76541b162d795e90c0bd273695d54 Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Thu, 13 Jun 2019 17:49:24 +0800 Subject: [PATCH 06/10] Update 1-js/09-classes/02-class-inheritance/article.md --- 1-js/09-classes/02-class-inheritance/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 0f9362f1de..f18008ae18 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -406,7 +406,7 @@ longEar.eat(); // Error: Maximum call stack size exceeded rabbit.eat.call(this); ``` -3. ...所以 `rabbit.eat` 不停地循环调用自己,因此它无法进一步地往原型链的更高层调用 +3. ...所以 `rabbit.eat` 不停地循环调用自己,因此它无法进一步地往原型链的更高层调用。 这个问题没法单独使用 `this` 来解决。 From 45fb1a2f50c069078424d4c88d53176b1633f038 Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Thu, 13 Jun 2019 17:49:46 +0800 Subject: [PATCH 07/10] Update 1-js/09-classes/02-class-inheritance/article.md --- 1-js/09-classes/02-class-inheritance/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index f18008ae18..f0c5197c53 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -456,7 +456,7 @@ longEar.eat(); // Long Ear eats. 在前面我们已经知道,通常函数都是 “自由” 的,并没有绑定到 JavaScript。正因如此,它们可以在对象之间复制,并且用另外一个 `this` 调用它。 -`[[HomeObject]]` 的存在违反了这个原则,因为方法记住了它们的对象。`[[HomeObject]]` 不能被修改,所以这个绑定时永久的。 +`[[HomeObject]]` 的存在违反了这个原则,因为方法记住了它们的对象。`[[HomeObject]]` 不能被修改,所以这个绑定是永久的。 在 JavaScript 语言中 `[[HomeObject]]` 仅被用于 `super`。所以,如果一个方法不使用 `super`,那么我们仍然可以视它为自由的并且可在对象之间复制。但是在 `super` 中可能出错。 From 87deb22ebb0621d40400b73ecb5dccc8a6064f25 Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Thu, 13 Jun 2019 17:50:05 +0800 Subject: [PATCH 08/10] Update 1-js/09-classes/02-class-inheritance/article.md --- 1-js/09-classes/02-class-inheritance/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index f0c5197c53..0c1317f331 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -494,7 +494,7 @@ tree.sayHi(); // I'm an animal (?!?) */!* ``` -调用 `tree.sayHi()` 显示 "I'm an animal"。绝对是错误的。 +调用 `tree.sayHi()` 显示 “I'm an animal”。绝对是错误的。 原因很简单: - 在 (\*) 行,`tree.sayHi` 方法从 `rabbit` 复制而来。也许我们只是想避免重复代码? From fa9d05ff4ef0f9ac1492002443dad4cfb19c0da5 Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Fri, 28 Jun 2019 20:15:25 +0800 Subject: [PATCH 09/10] Update article with review suggestions --- .../02-class-inheritance/article.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 0c1317f331..66e060772b 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -47,7 +47,7 @@ let rabbit = new Rabbit("My rabbit"); 现在,它们是完全独立的。 -但是,我们想要 `Rabbit` 继承自 `Animal`。换句话说,rabbits 应该基于 animals,具有访问 `Animal` 的权限,并使用自己的方法扩展它们。 +但是,我们想要 `Rabbit` 继承自 `Animal`。换句话说,rabbits 应该基于 animals,能够访问 `Animal` 中的方法,并使用自己的方法扩展它们。 要继承自另一个类,我们需要在 `{..}` 前指定 `“extends”` 和父类。 @@ -84,17 +84,17 @@ rabbit.run(5); // White Rabbit runs with speed 5. rabbit.hide(); // White Rabbit hides! ``` -现在 `Rabbit` 代码变简洁了一点,因为它默认以 `Animal` 作为其构造函数,同时它仍然能良好“运行”,就像 animals 一样。 +现在 `Rabbit` 代码变简洁了一点,因为它默认以 `Animal` 作为其构造函数,并且它能 `run`,就像 animals 一样。 -在其内部,`extends` 关键字将 `[[Prototype]]` 引用从 `Rabbit.prototype` 添加到 `Animal.prototype`: +在其内部,`extends` 关键字添加了 `[[Prototype]]` 引用:从 `Rabbit.prototype` 到 `Animal.prototype`: ![](animal-rabbit-extends.png) 因此,如果在 `Rabbit.prototype` 中没有找到某个方法,JavaScript 将会从 `Animal.prototype` 中获取它。 -我们可以回忆一下这一章 ,JavaScript 对内置对象使用相同的类型继承。例如,`Date.prototype.[[Prototype]]` 是 `Object.prototype`,所以 dates 有通用的对象方法。 +我们可以回忆一下这一章 ,JavaScript 内置对象同样也是基于原型继承的。例如,`Date.prototype.[[Prototype]]` 是 `Object.prototype`,所以 dates 有通用的对象方法。 -````smart header="`extends` 后允许任何表达式" +````smart header="`extends` 允许后接任何表达式" 类语法不仅可以指定一个类,还可以指定 `extends` 之后的任何表达式。 例如,一个生成父类的函数调用: @@ -114,7 +114,7 @@ new User().sayHi(); // Hello ``` 这里 `class User` 继承自 `f("Hello")` 的结果 -当我们使用函数根据许多条件生成类并且可以从它们继承时,这对于高级编程模式来说可能很有用。 +我们可以根据多种状况使用函数生成类,并继承它们,这对于高级编程模式来说可能很有用。 ```` ## 重写方法 @@ -194,7 +194,7 @@ class Rabbit extends Animal { } ``` -箭头函数中的 `super` 与 `stop()` 是相同的,所以它能按预期工作。如果我们在这里指定一个“普通”函数,那么将会抛出错误: +箭头函数中的 `super` 与 `stop()` 中的是相同的,所以它能按预期工作。如果我们在这里指定一个“普通”函数,那么将会抛出错误: ```js // Unexpected super @@ -250,7 +250,7 @@ class Rabbit extends Animal { *!* // 不生效! -let rabbit = new Rabbit("White Rabbit", 10); // Error: Error: this is not defined. +let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. */!* ``` @@ -305,7 +305,7 @@ alert(rabbit.earLength); // 10 ``` -## Super:内部基于 [[HomeObject]] 实现 +## Super 内部探究:[[HomeObject]] 让我们再深入的去研究下 `super`。顺便说一句,我们会发现一些有趣的事情。 @@ -450,11 +450,11 @@ longEar.eat(); // Long Ear eats. */!* ``` -它按照预期运行,基于 `[[HomeObject]]` 技巧。像 `longEar.eat` 这样的方法,知道 `[[HomeObject]]`,并且从它的原型中获取父类方法。并没有使用 `this`。 +它按照预期运行,基于 `[[HomeObject]]` 运行机制。像 `longEar.eat` 这样的方法,知道 `[[HomeObject]]`,并且从它的原型中获取父类方法。并没有使用 `this`。 ### 方法并不是 “自由” 的 -在前面我们已经知道,通常函数都是 “自由” 的,并没有绑定到 JavaScript。正因如此,它们可以在对象之间复制,并且用另外一个 `this` 调用它。 +在前面我们已经知道,通常函数都是 “自由” 的,并没有绑定到 JavaScript 中的对象。正因如此,它们可以在对象之间复制,并且用另外一个 `this` 调用它。 `[[HomeObject]]` 的存在违反了这个原则,因为方法记住了它们的对象。`[[HomeObject]]` 不能被修改,所以这个绑定是永久的。 @@ -513,7 +513,7 @@ tree.sayHi(); // I'm an animal (?!?) ```js run let animal = { - eat: function() { // 可以用短语法来书写:eat() {...} + eat: function() { // 可以使用简短写法:eat() {...} // ... } }; @@ -543,4 +543,4 @@ rabbit.eat(); // 错误调用 super(因为这里并没有 [[HomeObject]]) - 因此,将一个带有 `super` 的方法从一个对象复制到另一个对象是不安全的。 补充: -- 箭头函数没有自己的 `this` 或 `super`,所以它们能很好的适应周围的环境。 +- 箭头函数没有自己的 `this` 或 `super`,所以它们能融入到就近的上下文,像透明似的。 From 4935423a415486c0357581ce57d905907f61c82c Mon Sep 17 00:00:00 2001 From: LeviDing Date: Mon, 1 Jul 2019 09:13:04 +0800 Subject: [PATCH 10/10] Update article.md --- 1-js/09-classes/02-class-inheritance/article.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 66e060772b..e7fe0a5702 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -27,7 +27,7 @@ let animal = new Animal("My animal"); ![](rabbit-animal-independent-animal.png) -...和 `Rabbit`: +...和 `Rabbit`: ```js class Rabbit { @@ -112,7 +112,7 @@ class User extends f("Hello") {} new User().sayHi(); // Hello ``` -这里 `class User` 继承自 `f("Hello")` 的结果 +这里 `class User` 继承自 `f("Hello")` 的结果。 我们可以根据多种状况使用函数生成类,并继承它们,这对于高级编程模式来说可能很有用。 ```` @@ -209,7 +209,7 @@ setTimeout(function() { super.stop() }, 1000); 到目前为止,`Rabbit` 还没有自己的 `constructor`。 -根据 [规范](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation),如果一个类继承了另一个类并且没有 `constructor`,那么将生成以下 “空” `constructor`: +根据 [规范](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation),如果一个类继承了另一个类并且没有 `constructor`,那么将生成以下“空” `constructor`: ```js class Rabbit extends Animal { @@ -452,7 +452,7 @@ longEar.eat(); // Long Ear eats. 它按照预期运行,基于 `[[HomeObject]]` 运行机制。像 `longEar.eat` 这样的方法,知道 `[[HomeObject]]`,并且从它的原型中获取父类方法。并没有使用 `this`。 -### 方法并不是 “自由” 的 +### 方法并不是“自由”的 在前面我们已经知道,通常函数都是 “自由” 的,并没有绑定到 JavaScript 中的对象。正因如此,它们可以在对象之间复制,并且用另外一个 `this` 调用它。