minor
This commit is contained in:
parent
800d47c1e1
commit
f96872425d
2 changed files with 50 additions and 35 deletions
|
@ -17,7 +17,7 @@ class Animal {
|
||||||
}
|
}
|
||||||
stop() {
|
stop() {
|
||||||
this.speed = 0;
|
this.speed = 0;
|
||||||
alert(`${this.name} stopped.`);
|
alert(`${this.name} stands still.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ class Animal {
|
||||||
}
|
}
|
||||||
stop() {
|
stop() {
|
||||||
this.speed = 0;
|
this.speed = 0;
|
||||||
alert(`${this.name} stopped.`);
|
alert(`${this.name} stands still.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ class Animal {
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.speed = 0;
|
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");
|
let rabbit = new Rabbit("White Rabbit");
|
||||||
|
|
||||||
rabbit.run(5); // White Rabbit runs with speed 5.
|
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.
|
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:
|
The difference is:
|
||||||
|
|
||||||
- When a normal constructor runs, it creates an empty object as `this` and continues with it.
|
- 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 it. It expects the parent constructor to do this job.
|
- 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
|
```js run
|
||||||
class Animal {
|
class Animal {
|
||||||
|
@ -306,16 +306,24 @@ alert(rabbit.earLength); // 10
|
||||||
|
|
||||||
## Super: internals, [[HomeObject]]
|
## 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.
|
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!
|
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.
|
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.
|
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__`:
|
In the example below, `rabbit.__proto__ = animal`. Now let's try: in `rabbit.eat()` we'll call `animal.eat()`, using `this.__proto__`:
|
||||||
|
|
||||||
```js run
|
```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.
|
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
|
```js run
|
||||||
let animal = {
|
let animal = {
|
||||||
|
@ -468,6 +476,7 @@ let animal = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// rabbit inherits from animal
|
||||||
let rabbit = {
|
let rabbit = {
|
||||||
__proto__: animal,
|
__proto__: animal,
|
||||||
sayHi() {
|
sayHi() {
|
||||||
|
@ -481,6 +490,7 @@ let plant = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// tree inherits from plant
|
||||||
let tree = {
|
let tree = {
|
||||||
__proto__: plant,
|
__proto__: plant,
|
||||||
*!*
|
*!*
|
||||||
|
@ -497,9 +507,11 @@ A call to `tree.sayHi()` shows "I'm an animal". Definitevely wrong.
|
||||||
|
|
||||||
The reason is simple:
|
The reason is simple:
|
||||||
- In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication?
|
- 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`.
|
- 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:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Methods, not function properties
|
### Methods, not function properties
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
|
||||||
# Static properties and methods
|
# Static properties and methods
|
||||||
|
|
||||||
We can also assign a method to the class function, not to its `"prototype"`. Such methods are called *static*.
|
We can also assign a method to the class function itself, not to its `"prototype"`. Such methods are called *static*.
|
||||||
|
|
||||||
An example:
|
In a class, they are prepended by `static` keyword, like this:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
class User {
|
class User {
|
||||||
|
@ -17,7 +17,7 @@ class User {
|
||||||
User.staticMethod(); // true
|
User.staticMethod(); // true
|
||||||
```
|
```
|
||||||
|
|
||||||
That actually does the same as assigning it as a property:
|
That actually does the same as assigning it as a property directly:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class User() { }
|
class User() { }
|
||||||
|
@ -27,11 +27,11 @@ User.staticMethod = function() {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
The value of `this` inside `User.staticMethod()` is the class constructor `User` itself (the "object before dot" rule).
|
The value of `this` in `User.staticMethod()` call is the class constructor `User` itself (the "object before dot" rule).
|
||||||
|
|
||||||
Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it.
|
Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it.
|
||||||
|
|
||||||
For instance, we have `Article` objects and need a function to compare them. The natural choice would be `Article.compare`, like this:
|
For instance, we have `Article` objects and need a function to compare them. A natural solution would be to add `Article.compare` method, like this:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
class Article {
|
class Article {
|
||||||
|
@ -61,13 +61,13 @@ articles.sort(Article.compare);
|
||||||
alert( articles[0].title ); // CSS
|
alert( articles[0].title ); // CSS
|
||||||
```
|
```
|
||||||
|
|
||||||
Here `Article.compare` stands "over" the articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
|
Here `Article.compare` stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
|
||||||
|
|
||||||
Another example would be a so-called "factory" method. Imagine, we need few ways to create an article:
|
Another example would be a so-called "factory" method. Imagine, we need few ways to create an article:
|
||||||
|
|
||||||
1. Create by given parameters (`title`, `date` etc).
|
1. Create by given parameters (`title`, `date` etc).
|
||||||
2. Create an empty article with today's date.
|
2. Create an empty article with today's date.
|
||||||
3. ...
|
3. ...or else somehow.
|
||||||
|
|
||||||
The first way can be implemented by the constructor. And for the second one we can make a static method of the class.
|
The first way can be implemented by the constructor. And for the second one we can make a static method of the class.
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ Article.remove({id: 12345});
|
||||||
|
|
||||||
[recent browser=Chrome]
|
[recent browser=Chrome]
|
||||||
|
|
||||||
Static properties are also possible, just like regular class properties:
|
Static properties are also possible, they look like regular class properties, but prepended by `static`:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
class Article {
|
class Article {
|
||||||
|
@ -123,9 +123,9 @@ That is the same as a direct assignment to `Article`:
|
||||||
Article.publisher = "Ilya Kantor";
|
Article.publisher = "Ilya Kantor";
|
||||||
```
|
```
|
||||||
|
|
||||||
## Statics and inheritance
|
## Inheritance of static methods
|
||||||
|
|
||||||
Statics are inherited, we can access `Parent.method` as `Child.method`.
|
Static methods are inherited.
|
||||||
|
|
||||||
For instance, `Animal.compare` in the code below is inherited and accessible as `Rabbit.compare`:
|
For instance, `Animal.compare` in the code below is inherited and accessible as `Rabbit.compare`:
|
||||||
|
|
||||||
|
@ -169,36 +169,39 @@ rabbits.sort(Rabbit.compare);
|
||||||
rabbits[0].run(); // Black Rabbit runs with speed 5.
|
rabbits[0].run(); // Black Rabbit runs with speed 5.
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we can call `Rabbit.compare` assuming that the inherited `Animal.compare` will be called.
|
Now when we can call `Rabbit.compare`, the inherited `Animal.compare` will be called.
|
||||||
|
|
||||||
How does it work? Again, using prototypes. As you might have already guessed, `extends` gives `Rabbit` the `[[Prototype]]` reference to `Animal`.
|
How does it work? Again, using prototypes. As you might have already guessed, `extends` 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.
|
So, `Rabbit extends Animal` creates two `[[Prototype]]` references:
|
||||||
|
|
||||||
Here, let's check that:
|
1. `Rabbit` function prototypally inherits from `Animal` function.
|
||||||
|
2. `Rabbit.prototype` prototypally inherits from `Animal.prototype`.
|
||||||
|
|
||||||
|
As the result, inheritance works both for regular and static methods.
|
||||||
|
|
||||||
|
Here, let's check that by code:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
class Animal {}
|
class Animal {}
|
||||||
class Rabbit extends Animal {}
|
class Rabbit extends Animal {}
|
||||||
|
|
||||||
// for static properties and methods
|
// for statics
|
||||||
alert(Rabbit.__proto__ === Animal); // true
|
alert(Rabbit.__proto__ === Animal); // true
|
||||||
|
|
||||||
// the next step up leads to Function.prototype
|
// for regular methods
|
||||||
alert(Animal.__proto__ === Function.prototype); // true
|
|
||||||
|
|
||||||
// the "normal" prototype chain for object methods
|
|
||||||
alert(Rabbit.prototype.__proto__ === Animal.prototype);
|
alert(Rabbit.prototype.__proto__ === Animal.prototype);
|
||||||
```
|
```
|
||||||
|
|
||||||
This way `Rabbit` has access to all static methods of `Animal`.
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
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.
|
Static methods are used for the functionality that belongs to the class "as a whole", doesn't relate to a concrete class instance.
|
||||||
|
|
||||||
|
For example, a method for comparison `Article.compare(article1, article2)` or a factory method `Article.createTodays()`.
|
||||||
|
|
||||||
|
They are labeled by the word `static` in class declaration.
|
||||||
|
|
||||||
Static properties are used when we'd like to store class-level data, also not bound to an instance.
|
Static properties are used when we'd like to store class-level data, also not bound to an instance.
|
||||||
|
|
||||||
|
@ -214,7 +217,7 @@ class MyClass {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
That's technically the same as assigning to the class itself:
|
Technically, static declaration is the same as assigning to the class itself:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
MyClass.property = ...
|
MyClass.property = ...
|
||||||
|
@ -223,4 +226,4 @@ MyClass.method = ...
|
||||||
|
|
||||||
Static properties are inherited.
|
Static properties are inherited.
|
||||||
|
|
||||||
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`.
|
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`.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue