You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 1-js/09-classes/02-class-inheritance/article.md
+23-11
Original file line number
Diff line number
Diff line change
@@ -17,7 +17,7 @@ class Animal {
17
17
}
18
18
stop() {
19
19
this.speed=0;
20
-
alert(`${this.name}stopped.`);
20
+
alert(`${this.name}stands still.`);
21
21
}
22
22
}
23
23
@@ -65,7 +65,7 @@ class Animal {
65
65
}
66
66
stop() {
67
67
this.speed=0;
68
-
alert(`${this.name}stopped.`);
68
+
alert(`${this.name}stands still.`);
69
69
}
70
70
}
71
71
@@ -155,7 +155,7 @@ class Animal {
155
155
156
156
stop() {
157
157
this.speed = 0;
158
-
alert(`${this.name} stopped.`);
158
+
alert(`${this.name} stands still.`);
159
159
}
160
160
161
161
}
@@ -176,7 +176,7 @@ class Rabbit extends Animal {
176
176
let rabbit = new Rabbit("White Rabbit");
177
177
178
178
rabbit.run(5); // White Rabbit runs with speed 5.
179
-
rabbit.stop(); // White Rabbit stopped. White rabbit hides!
179
+
rabbit.stop(); // White Rabbit stands still. White rabbit hides!
180
180
```
181
181
182
182
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
265
265
266
266
The difference is:
267
267
268
-
- When a normal constructor runs, it creates an empty object as `this`and continues with it.
269
-
- But when a derived constructor runs, it doesn't do it. It expects the parent constructor to do this job.
268
+
- When a normal constructor runs, it creates an empty object and assigns it to `this`.
269
+
- But when a derived constructor runs, it doesn't do this. It expects the parent constructor to do this job.
270
270
271
-
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.
271
+
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.
272
272
273
-
For `Rabbit` to work, we need to call `super()` before using `this`, like here:
273
+
For `Rabbit`constructor to work, it needs to call `super()` before using `this`, like here:
If you're reading the tutorial for the first time - this section may be skipped.
311
+
312
+
It's about the internal mechanisms behind inheritance and `super`.
313
+
```
314
+
309
315
Let's get a little deeper under the hood of `super`. We'll see some interesting things by the way.
310
316
311
317
First to say, from all that we've learned till now, it's impossible for `super` to work at all!
312
318
313
-
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.
319
+
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?
314
320
315
321
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.
316
322
317
323
Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity.
318
324
325
+
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.
326
+
319
327
In the example below, `rabbit.__proto__ = animal`. Now let's try: in `rabbit.eat()` we'll call `animal.eat()`, using `this.__proto__`:
320
328
321
329
```js run
@@ -459,7 +467,7 @@ The very existance of `[[HomeObject]]` violates that principle, because methods
459
467
460
468
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. Butwith`super` things may go wrong.
461
469
462
-
Here's the demo of a wrong `super` call:
470
+
Here's the demo of a wrong `super` result after copying:
463
471
464
472
```js run
465
473
let animal = {
@@ -468,6 +476,7 @@ let animal = {
468
476
}
469
477
};
470
478
479
+
// rabbit inherits from animal
471
480
let rabbit = {
472
481
__proto__: animal,
473
482
sayHi() {
@@ -481,6 +490,7 @@ let plant = {
481
490
}
482
491
};
483
492
493
+
// tree inherits from plant
484
494
let tree = {
485
495
__proto__: plant,
486
496
*!*
@@ -497,9 +507,11 @@ A call to `tree.sayHi()` shows "I'm an animal". Definitevely wrong.
497
507
498
508
The reason is simple:
499
509
- In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication?
500
-
-So its`[[HomeObject]]` is `rabbit`, as it was created in`rabbit`. There's no way to change `[[HomeObject]]`.
510
+
-Its`[[HomeObject]]` is `rabbit`, as it was created in`rabbit`. There's no way to change `[[HomeObject]]`.
501
511
- The code of `tree.sayHi()` has `super.sayHi()` inside. It goes up from `rabbit` and takes the method from `animal`.
rabbits[0].run(); // Black Rabbit runs with speed 5.
170
170
```
171
171
172
-
Now we can call `Rabbit.compare` assuming that the inherited `Animal.compare` will be called.
172
+
Now when we can call `Rabbit.compare`, the inherited `Animal.compare` will be called.
173
173
174
174
How does it work? Again, using prototypes. As you might have already guessed, `extends` gives `Rabbit` the `[[Prototype]]` reference to `Animal`.
175
175
176
-
177
176

178
177
179
-
So, `Rabbit` function now inherits from `Animal` function. And `Animal` function normally has `[[Prototype]]` referencing `Function.prototype`, because it doesn't `extend` anything.
178
+
So, `Rabbit extends Animal` creates two `[[Prototype]]` references:
179
+
180
+
1.`Rabbit` function prototypally inherits from `Animal` function.
181
+
2.`Rabbit.prototype` prototypally inherits from `Animal.prototype`.
180
182
181
-
Here, let's check that:
183
+
As the result, inheritance works both for regular and static methods.
This way `Rabbit` has access to all static methods of `Animal`.
198
-
199
198
## Summary
200
199
201
-
Static methods are used for the functionality that doesn't relate to a concrete class instance, doesn't require an instance to exist, but rather belongs to the class as a whole, like `Article.compare` -- a generic method to compare two articles.
200
+
Static methods are used for the functionality that belongs to the class "as a whole", doesn't relate to a concrete class instance.
201
+
202
+
For example, a method for comparison `Article.compare(article1, article2)` or a factory method `Article.createTodays()`.
203
+
204
+
They are labeled by the word `static` in class declaration.
202
205
203
206
Static properties are used when we'd like to store class-level data, also not bound to an instance.
204
207
@@ -214,7 +217,7 @@ class MyClass {
214
217
}
215
218
```
216
219
217
-
That's technically the same as assigning to the class itself:
220
+
Technically, static declaration is the same as assigning to the class itself:
218
221
219
222
```js
220
223
MyClass.property=...
@@ -223,4 +226,4 @@ MyClass.method = ...
223
226
224
227
Static properties are inherited.
225
228
226
-
Technically, for`class B extends A` the prototype of the class `B` itself points to `A`: `B.[[Prototype]] = A`. So if a field is not found in `B`, the search continues in `A`.
229
+
For`class B extends A` the prototype of the class `B` itself points to `A`: `B.[[Prototype]] = A`. So if a field is not found in `B`, the search continues in `A`.
0 commit comments