up
|
@ -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) min = '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) min = '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: 63 KiB |
|
@ -0,0 +1,87 @@
|
|||
The answer has two parts.
|
||||
|
||||
The first, an easy one is that the inheriting class needs to call `super()` in the constructor. 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 we can access 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
|
||||
*/!*
|
||||
```
|
||||
|
||||
And if we don't use `extends`, then `class Rabbit` does not get the second reference.
|
||||
|
||||
Please compare with it:
|
||||
|
||||
```js run
|
||||
class Rabbit {}
|
||||
|
||||
alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
|
||||
alert( Rabbit.__proto__ === Object ); // (2) false (!)
|
||||
|
||||
*!*
|
||||
// error, no such function in Rabbit
|
||||
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error
|
||||
*/!*
|
||||
```
|
||||
|
||||
For the simple `class Rabbit`, the `Rabbit` function has the same prototype
|
||||
|
||||
```js run
|
||||
class Rabbit {}
|
||||
|
||||
// instead of (2) that's correct for Rabbit (just like any function):
|
||||
alert( Rabbit.__proto__ === Function.prototype );
|
||||
```
|
||||
|
||||
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 demonstrated here:
|
||||
|
||||
```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
|
||||
*/!*
|
||||
```
|
||||
|
||||
So, is it correct to say that `"class Rabbit extends Object"` does exactly the same as `"class Rabbit"`, or not?
|
||||
|
||||
Will it work?
|
||||
|
||||
```js
|
||||
class Rabbit extends Object {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
let rabbit = new Rabbit("Rab");
|
||||
|
||||
alert( rabbit.hasOwnProperty('name') ); // true
|
||||
```
|
||||
|
||||
If it won't please fix the code.
|
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 71 KiB |
|
@ -0,0 +1,574 @@
|
|||
|
||||
# Class inheritance, super
|
||||
|
||||
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 `{..}`.
|
||||
|
||||
[cut]
|
||||
|
||||
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
|
||||
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, things are is a little bit tricky.
|
||||
|
||||
Till now, `Rabbit` had no 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.
|
||||
*/!*
|
||||
```
|
||||
|
||||
Wops! 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 that method? In other words, we need to take the `method` from the parent prototype of the current object. How, technically, we (or a JavaScript engine) can do it?
|
||||
|
||||
Maybe we can get it `[[Prototype]]` of `this`, as `this.__proto__.method`? Unfortunately, that won't work.
|
||||
|
||||
Let's try to do it. Without classes, using plain objects for sheer 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() {
|
||||
*!*
|
||||
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 here it works.
|
||||
|
||||
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 any more! 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 other words:
|
||||
|
||||
1. Inside `longEar.eat()`, we pass the call up to `rabbit.eat` giving it the same `this=longEar`.
|
||||
```js
|
||||
// inside longEar.eat() we have this = longEar
|
||||
this.__proto__.eat.call(this) // (**)
|
||||
// becomes
|
||||
longEar.__proto__.eat.call(this)
|
||||
// or
|
||||
rabbit.eat.call(this);
|
||||
```
|
||||
2. Inside `rabbit.eat`, we want to pass the call even higher in the chain, but `this=longEar`, so `this.__proto__.eat` is `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.
|
||||
|
||||

|
||||
|
||||
There problem is unsolvable, because `this` must always be the calling object itself, no matter which parent method is called. So its prototype will always be the immediate parent of the object. We can't go up the chain.
|
||||
|
||||
### `[[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]])
|
||||
*/!*
|
||||
```
|
||||
|
||||
## Static methods and inheritance
|
||||
|
||||
The `class` syntax supports inheritance for static properties too.
|
||||
|
||||
For instance:
|
||||
|
||||
```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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Inherit from 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.
|
||||
```
|
||||
|
||||
Now we can call `Rabbit.compare` assuming that the inherited `Animal.compare` will be called.
|
||||
|
||||
How does it work? Again, using prototypes. As you might have already guessed, extends also gives `Rabbit` the `[[Prototype]]` reference to `Animal`.
|
||||
|
||||
|
||||

|
||||
|
||||
So, `Rabbit` function now inherits from `Animal` function. And `Animal` function normally has `[[Prototype]]` referencing `Function.prototype`, because it doesn't `extend` anything.
|
||||
|
||||
Here, let's check that:
|
||||
|
||||
```js run
|
||||
class Animal {}
|
||||
class Rabbit extends Animal {}
|
||||
|
||||
// for static propertites and methods
|
||||
alert(Rabbit.__proto__ == Animal); // true
|
||||
|
||||
// and the next step is Function.prototype
|
||||
alert(Animal.__proto__ == Function.prototype); // true
|
||||
|
||||
// that's in addition to the "normal" prototype chain for object methods
|
||||
alert(Rabbit.prototype.__proto__ === Animal.prototype);
|
||||
```
|
||||
|
||||
This way `Rabbit` has access to all static methods of `Animal`.
|
||||
|
||||
Please note that built-in classes don't have such static `[[Prototype]]` reference. For instance, `Object` has `Object.defineProperty`, `Object.keys` and so on, but `Array`, `Date` etc do not inherit them.
|
||||
|
||||
Here's the picture structure for `Date` and `Object`:
|
||||
|
||||

|
||||
|
||||
Note, there's no link between `Date` and `Object`. Both `Object` and `Date` exist independently. `Date.prototype` inherits from `Object.prototype`, but that's all.
|
||||
|
||||
Such difference exists for historical reasons: there was no thought about class syntax and inheriting static methods at the dawn of JavaScript language.
|
||||
|
||||
## Natives are extendable
|
||||
|
||||
Built-in classes like Array, Map and others are extendable also.
|
||||
|
||||
For instance, here `PowerArray` inherits from the native `Array`:
|
||||
|
||||
```js run
|
||||
// add one more method to it (can do more)
|
||||
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
|
||||
```
|
||||
|
||||
Please note one very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type. They rely on the `constructor` property to do so.
|
||||
|
||||
In the example above,
|
||||
```js
|
||||
arr.constructor === PowerArray
|
||||
```
|
||||
|
||||
So when `arr.filter()` is called, it internally creates the new array of results exactly as `new PowerArray`. And we can keep using its methods further down the chain.
|
||||
|
||||
Even more, we can customize that behavior. The static getter `Symbol.species`, if exists, returns the constructor to use in such cases.
|
||||
|
||||
For example, here due to `Symbol.species` built-in methods like `map`, `filter` will return "normal" arrays:
|
||||
|
||||
```js run
|
||||
class PowerArray extends Array {
|
||||
isEmpty() {
|
||||
return this.length == 0;
|
||||
}
|
||||
|
||||
*!*
|
||||
// built-in methods will use this as the constructor
|
||||
static get [Symbol.species]() {
|
||||
return Array;
|
||||
}
|
||||
*/!*
|
||||
}
|
||||
|
||||
let arr = new PowerArray(1, 2, 5, 10, 50);
|
||||
alert(arr.isEmpty()); // false
|
||||
|
||||
// filter creates new array using arr.constructor[Symbol.species] as constructor
|
||||
let filteredArr = arr.filter(item => item >= 10);
|
||||
|
||||
*!*
|
||||
// filteredArr is not PowerArray, but Array
|
||||
*/!*
|
||||
alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
|
||||
```
|
||||
|
||||
We can use it in more advanced keys to strip extended functionality from resulting values if not needed. Or, maybe, to extend it even further.
|
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 41 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 |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 50 KiB |