refactor classes, add private, minor fixes
|
@ -0,0 +1,27 @@
|
|||
That's because the child constructor must call `super()`.
|
||||
|
||||
Here's the corrected code:
|
||||
|
||||
```js run
|
||||
class Animal {
|
||||
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Rabbit extends Animal {
|
||||
constructor(name) {
|
||||
*!*
|
||||
super(name);
|
||||
*/!*
|
||||
this.created = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
*!*
|
||||
let rabbit = new Rabbit("White Rabbit"); // ok now
|
||||
*/!*
|
||||
alert(rabbit.name); // White Rabbit
|
||||
```
|
|
@ -0,0 +1,30 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Error creating an instance
|
||||
|
||||
Here's the code with `Rabbit` extending `Animal`.
|
||||
|
||||
Unfortunately, `Rabbit` objects can't be created. What's wrong? Fix it.
|
||||
```js run
|
||||
class Animal {
|
||||
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Rabbit extends Animal {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.created = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
*!*
|
||||
let rabbit = new Rabbit("White Rabbit"); // Error: this is not defined
|
||||
*/!*
|
||||
alert(rabbit.name);
|
||||
```
|
|
@ -0,0 +1,34 @@
|
|||
class Clock {
|
||||
constructor({ template }) {
|
||||
this._template = template;
|
||||
}
|
||||
|
||||
_render() {
|
||||
let date = new Date();
|
||||
|
||||
let hours = date.getHours();
|
||||
if (hours < 10) hours = '0' + hours;
|
||||
|
||||
let mins = date.getMinutes();
|
||||
if (mins < 10) mins = '0' + mins;
|
||||
|
||||
let secs = date.getSeconds();
|
||||
if (secs < 10) secs = '0' + secs;
|
||||
|
||||
let output = this._template
|
||||
.replace('h', hours)
|
||||
.replace('m', mins)
|
||||
.replace('s', secs);
|
||||
|
||||
console.log(output);
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearInterval(this._timer);
|
||||
}
|
||||
|
||||
start() {
|
||||
this._render();
|
||||
this._timer = setInterval(() => this._render(), 1000);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
class ExtendedClock extends Clock {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
let { precision=1000 } = options;
|
||||
this._precision = precision;
|
||||
}
|
||||
|
||||
start() {
|
||||
this._render();
|
||||
this._timer = setInterval(() => this._render(), this._precision);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Console clock</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script src="clock.js"></script>
|
||||
<script src="extended-clock.js"></script>
|
||||
|
||||
<script>
|
||||
let lowResolutionClock = new ExtendedClock({
|
||||
template: 'h:m:s',
|
||||
precision: 10000
|
||||
});
|
||||
|
||||
lowResolutionClock.start();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,34 @@
|
|||
class Clock {
|
||||
constructor({ template }) {
|
||||
this._template = template;
|
||||
}
|
||||
|
||||
_render() {
|
||||
let date = new Date();
|
||||
|
||||
let hours = date.getHours();
|
||||
if (hours < 10) hours = '0' + hours;
|
||||
|
||||
let mins = date.getMinutes();
|
||||
if (mins < 10) mins = '0' + mins;
|
||||
|
||||
let secs = date.getSeconds();
|
||||
if (secs < 10) secs = '0' + secs;
|
||||
|
||||
let output = this._template
|
||||
.replace('h', hours)
|
||||
.replace('m', mins)
|
||||
.replace('s', secs);
|
||||
|
||||
console.log(output);
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearInterval(this._timer);
|
||||
}
|
||||
|
||||
start() {
|
||||
this._render();
|
||||
this._timer = setInterval(() => this._render(), 1000);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Console clock</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- source clock -->
|
||||
<script src="clock.js"></script>
|
||||
<script>
|
||||
let clock = new Clock({
|
||||
template: 'h:m:s'
|
||||
});
|
||||
clock.start();
|
||||
|
||||
|
||||
/* Your class should work like this: */
|
||||
|
||||
/*
|
||||
|
||||
let lowResolutionClock = new ExtendedClock({
|
||||
template: 'h:m:s',
|
||||
precision: 10000
|
||||
});
|
||||
|
||||
lowResolutionClock.start();
|
||||
*/
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,12 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Extended clock
|
||||
|
||||
We've got a `Clock` class. As of now, it prints the time every second.
|
||||
|
||||
Create a new class `ExtendedClock` that inherits from `Clock` and adds the parameter `precision` -- the number of `ms` between "ticks". Should be `1000` (1 second) by default.
|
||||
|
||||
- Your code should be in the file `extended-clock.js`
|
||||
- Don't modify the original `clock.js`. Extend it.
|
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 62 KiB |
|
@ -0,0 +1,81 @@
|
|||
First, let's see why the latter code doesn't work.
|
||||
|
||||
The reason becomes obvious if we try to run it. An inheriting class constructor must call `super()`. Otherwise `"this"` won't be "defined".
|
||||
|
||||
So here's the fix:
|
||||
|
||||
```js run
|
||||
class Rabbit extends Object {
|
||||
constructor(name) {
|
||||
*!*
|
||||
super(); // need to call the parent constructor when inheriting
|
||||
*/!*
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
let rabbit = new Rabbit("Rab");
|
||||
|
||||
alert( rabbit.hasOwnProperty('name') ); // true
|
||||
```
|
||||
|
||||
But that's not all yet.
|
||||
|
||||
Even after the fix, there's still important difference in `"class Rabbit extends Object"` versus `class Rabbit`.
|
||||
|
||||
As we know, the "extends" syntax sets up two prototypes:
|
||||
|
||||
1. Between `"prototype"` of the constructor functions (for methods).
|
||||
2. Between the constructor functions itself (for static methods).
|
||||
|
||||
In our case, for `class Rabbit extends Object` it means:
|
||||
|
||||
```js run
|
||||
class Rabbit extends Object {}
|
||||
|
||||
alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
|
||||
alert( Rabbit.__proto__ === Object ); // (2) true
|
||||
```
|
||||
|
||||
So `Rabbit` now provides access to static methods of `Object` via `Rabbit`, like this:
|
||||
|
||||
```js run
|
||||
class Rabbit extends Object {}
|
||||
|
||||
*!*
|
||||
// normally we call Object.getOwnPropertyNames
|
||||
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b
|
||||
*/!*
|
||||
```
|
||||
|
||||
But if we don't have `extends Object`, then `Rabbit.__proto__` is not set to `Object`.
|
||||
|
||||
Here's the demo:
|
||||
|
||||
```js run
|
||||
class Rabbit {}
|
||||
|
||||
alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
|
||||
alert( Rabbit.__proto__ === Object ); // (2) false (!)
|
||||
alert( Rabbit.__proto__ === Function.prototype ); // as any function by default
|
||||
|
||||
*!*
|
||||
// error, no such function in Rabbit
|
||||
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error
|
||||
*/!*
|
||||
```
|
||||
|
||||
So `Rabbit` doesn't provide access to static methods of `Object` in that case.
|
||||
|
||||
By the way, `Function.prototype` has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`.
|
||||
|
||||
Here's the picture:
|
||||
|
||||

|
||||
|
||||
So, to put it short, there are two differences:
|
||||
|
||||
| class Rabbit | class Rabbit extends Object |
|
||||
|--------------|------------------------------|
|
||||
| -- | needs to call `super()` in constructor |
|
||||
| `Rabbit.__proto__ === Function.prototype` | `Rabbit.__proto__ === Object` |
|
|
@ -0,0 +1,43 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Class extends Object?
|
||||
|
||||
As we know, all objects normally inherit from `Object.prototype` and get access to "generic" object methods like `hasOwnProperty` etc.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
class Rabbit {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
let rabbit = new Rabbit("Rab");
|
||||
|
||||
*!*
|
||||
// hasOwnProperty method is from Object.prototype
|
||||
// rabbit.__proto__ === Object.prototype
|
||||
alert( rabbit.hasOwnProperty('name') ); // true
|
||||
*/!*
|
||||
```
|
||||
|
||||
But if we spell it out explicitly like `"class Rabbit extends Object"`, then the result would be different from a simple `"class Rabbit"`?
|
||||
|
||||
What's the difference?
|
||||
|
||||
Here's an example of such code (it doesn't work -- why? fix it?):
|
||||
|
||||
```js
|
||||
class Rabbit extends Object {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
let rabbit = new Rabbit("Rab");
|
||||
|
||||
alert( rabbit.hasOwnProperty('name') ); // true
|
||||
```
|
BIN
1-js/09-classes/03-class-inheritance/animal-rabbit-extends.png
Normal file
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 72 KiB |
431
1-js/09-classes/03-class-inheritance/article.md
Normal file
|
@ -0,0 +1,431 @@
|
|||
|
||||
# Class inheritance
|
||||
|
||||
Classes can extend one another. There's a nice syntax, technically based on the prototypal inheritance.
|
||||
|
||||
To inherit from another class, we should specify `"extends"` and the parent class before the brackets `{..}`.
|
||||
|
||||
Here `Rabbit` inherits from `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.`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
*!*
|
||||
// Inherit from 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!
|
||||
```
|
||||
|
||||
The `extends` keyword actually adds a `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`, just as you expect it to be, and as we've seen before.
|
||||
|
||||

|
||||
|
||||
So now `rabbit` has access both to its own methods and to methods of `Animal`.
|
||||
|
||||
````smart header="Any expression is allowed after `extends`"
|
||||
Class syntax allows to specify not just a class, but any expression after `extends`.
|
||||
|
||||
For instance, a function call that generates the parent class:
|
||||
|
||||
```js run
|
||||
function f(phrase) {
|
||||
return class {
|
||||
sayHi() { alert(phrase) }
|
||||
}
|
||||
}
|
||||
|
||||
*!*
|
||||
class User extends f("Hello") {}
|
||||
*/!*
|
||||
|
||||
new User().sayHi(); // Hello
|
||||
```
|
||||
Here `class User` inherits from the result of `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`.
|
||||
|
||||
If we specify our own `stop` in `Rabbit`, then it will be used instead:
|
||||
|
||||
```js
|
||||
class Rabbit extends Animal {
|
||||
stop() {
|
||||
// ...this will be used for 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.
|
||||
|
||||
Classes provide `"super"` keyword for that.
|
||||
|
||||
- `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 {
|
||||
|
||||
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(); // call parent stop
|
||||
this.hide(); // and then hide
|
||||
}
|
||||
*/!*
|
||||
}
|
||||
|
||||
let rabbit = new Rabbit("White Rabbit");
|
||||
|
||||
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.
|
||||
|
||||
````smart header="Arrow functions have no `super`"
|
||||
As was mentioned in the chapter <info:arrow-functions>, arrow functions do not have `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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
// Unexpected 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`.
|
||||
|
||||
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 `constructor` is generated:
|
||||
|
||||
```js
|
||||
class Rabbit extends Animal {
|
||||
// generated for extending classes without own constructors
|
||||
*!*
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
}
|
||||
*/!*
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Now let's add a custom constructor to `Rabbit`. It will specify the `earLength` in addition to `name`:
|
||||
|
||||
```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;
|
||||
}
|
||||
*/!*
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
*!*
|
||||
// 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`.
|
||||
|
||||
...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"`.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
For `Rabbit` to work, we need to call `super()` before using `this`, like here:
|
||||
|
||||
```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: internals, [[HomeObject]]
|
||||
|
||||
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.
|
||||
|
||||
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, how to retrieve the `method`? Naturally, we need to take the `method` from the prototype of the current object. How, technically, we (or a JavaScript engine) can do it?
|
||||
|
||||
Maybe we can get the method from `[[Prototype]]` of `this`, as `this.__proto__.method`? Unfortunately, that doesn't work.
|
||||
|
||||
Let's try to do it. Without classes, using plain objects for the sake of simplicity.
|
||||
|
||||
Here, `rabbit.eat()` should call `animal.eat()` method of the parent object:
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
name: "Animal",
|
||||
eat() {
|
||||
alert(`${this.name} eats.`);
|
||||
}
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
__proto__: animal,
|
||||
name: "Rabbit",
|
||||
eat() {
|
||||
*!*
|
||||
// that's how super.eat() could presumably work
|
||||
this.__proto__.eat.call(this); // (*)
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
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.
|
||||
|
||||
And in the code above it actually works as intended: we have the correct `alert`.
|
||||
|
||||
Now let's add one more object to the chain. We'll see how things break:
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
name: "Animal",
|
||||
eat() {
|
||||
alert(`${this.name} eats.`);
|
||||
}
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
__proto__: animal,
|
||||
eat() {
|
||||
// ...bounce around rabbit-style and call parent (animal) method
|
||||
this.__proto__.eat.call(this); // (*)
|
||||
}
|
||||
};
|
||||
|
||||
let longEar = {
|
||||
__proto__: rabbit,
|
||||
eat() {
|
||||
// ...do something with long ears and call parent (rabbit) method
|
||||
this.__proto__.eat.call(this); // (**)
|
||||
}
|
||||
};
|
||||
|
||||
*!*
|
||||
longEar.eat(); // Error: Maximum call stack size exceeded
|
||||
*/!*
|
||||
```
|
||||
|
||||
The code doesn't work anymore! We can see the error trying to call `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.
|
||||
|
||||
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.
|
||||
|
||||
Here's the picture of what happens:
|
||||
|
||||

|
||||
|
||||
1. Inside `longEar.eat()`, the line `(**)` calls `rabbit.eat` providing it with `this=longEar`.
|
||||
```js
|
||||
// inside longEar.eat() we have 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`!
|
||||
|
||||
```js
|
||||
// inside rabbit.eat() we also have 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.
|
||||
|
||||
The problem can't be solved by using `this` alone.
|
||||
|
||||
### `[[HomeObject]]`
|
||||
|
||||
To provide the solution, JavaScript adds one more special internal property for functions: `[[HomeObject]]`.
|
||||
|
||||
**When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object.**
|
||||
|
||||
This actually violates the idea of "unbound" functions, because methods remember their objects. And `[[HomeObject]]` can't be changed, so this bound is forever. So that's a very important change in the language.
|
||||
|
||||
But this change is safe. `[[HomeObject]]` is used only for calling parent methods in `super`, to resolve the prototype. So it doesn't break compatibility.
|
||||
|
||||
Let's see how it works for `super` -- again, using plain objects:
|
||||
|
||||
```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.
|
||||
*/!*
|
||||
```
|
||||
|
||||
Every method remembers its object in the internal `[[HomeObject]]` property. Then `super` uses it to resolve the parent prototype.
|
||||
|
||||
`[[HomeObject]]` is defined for methods defined both in classes and in plain objects. But for objects, methods must be specified exactly the given way: as `method()`, not as `"method: function()"`.
|
||||
|
||||
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() {...}
|
||||
// ...
|
||||
}
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
__proto__: animal,
|
||||
eat: function() {
|
||||
super.eat();
|
||||
}
|
||||
};
|
||||
|
||||
*!*
|
||||
rabbit.eat(); // Error calling super (because there's no [[HomeObject]])
|
||||
*/!*
|
||||
```
|
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 44 KiB |
BIN
1-js/09-classes/03-class-inheritance/this-super-loop.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
1-js/09-classes/03-class-inheritance/this-super-loop@2x.png
Normal file
After Width: | Height: | Size: 54 KiB |