This commit is contained in:
Ilya Kantor 2019-08-04 12:48:45 +03:00
parent 800d47c1e1
commit f96872425d
2 changed files with 50 additions and 35 deletions

View file

@ -17,7 +17,7 @@ class Animal {
}
stop() {
this.speed = 0;
alert(`${this.name} stopped.`);
alert(`${this.name} stands still.`);
}
}
@ -65,7 +65,7 @@ class Animal {
}
stop() {
this.speed = 0;
alert(`${this.name} stopped.`);
alert(`${this.name} stands still.`);
}
}
@ -155,7 +155,7 @@ class Animal {
stop() {
this.speed = 0;
alert(`${this.name} stopped.`);
alert(`${this.name} stands still.`);
}
}
@ -176,7 +176,7 @@ class Rabbit extends Animal {
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(); // White Rabbit stands still. White rabbit hides!
```
Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the process.
@ -265,12 +265,12 @@ In JavaScript, there's a distinction between a "constructor function of an inher
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.
- When a normal constructor runs, it creates an empty object and assigns it to `this`.
- But when a derived constructor runs, it doesn't do this. It expects the parent constructor to do this job.
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.
So if we're making a constructor of our own, then we must call `super`, because otherwise the object for `this` won't be created. And we'll get an error.
For `Rabbit` to work, we need to call `super()` before using `this`, like here:
For `Rabbit` constructor to work, it needs to call `super()` before using `this`, like here:
```js run
class Animal {
@ -306,16 +306,24 @@ alert(rabbit.earLength); // 10
## Super: internals, [[HomeObject]]
```warn header="Advanced information"
If you're reading the tutorial for the first time - this section may be skipped.
It's about the internal mechanisms behind inheritance and `super`.
```
Let's get a little deeper under the hood of `super`. We'll see some interesting things by the way.
First to say, from all that we've learned till now, it's impossible for `super` to work at all!
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.
Yeah, indeed, let's ask ourselves, how it should technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, the engine needs to get the `method` from the prototype of the current object. But how?
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.
Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity.
You may skip this part and go below to the `[[HomeObject]]` subsection if you don't want to know the details. That won't harm. Or read on if you're interested in understanding things in-depth.
In the example below, `rabbit.__proto__ = animal`. Now let's try: in `rabbit.eat()` we'll call `animal.eat()`, using `this.__proto__`:
```js run
@ -459,7 +467,7 @@ The very existance of `[[HomeObject]]` violates that principle, because methods
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:
Here's the demo of a wrong `super` result after copying:
```js run
let animal = {
@ -468,6 +476,7 @@ let animal = {
}
};
// rabbit inherits from animal
let rabbit = {
__proto__: animal,
sayHi() {
@ -481,6 +490,7 @@ let plant = {
}
};
// tree inherits from plant
let tree = {
__proto__: plant,
*!*
@ -497,9 +507,11 @@ 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]]`.
- 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`.
Here's the diagram of what happens:
![](super-homeobject-wrong.svg)
### Methods, not function properties