fixes #1979
This commit is contained in:
parent
340ce43421
commit
da9849d61a
2 changed files with 102 additions and 26 deletions
|
@ -331,7 +331,7 @@ alert(user.name); // John
|
||||||
alert(User.prototype.name); // undefined
|
alert(User.prototype.name); // undefined
|
||||||
```
|
```
|
||||||
|
|
||||||
Technically, they are processed after the constructor has done it's job, and we can use for them complex expressions and function calls:
|
We can also assign values using more complex expressions and function calls:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
class User {
|
class User {
|
||||||
|
@ -344,6 +344,7 @@ let user = new User();
|
||||||
alert(user.name); // John
|
alert(user.name); // John
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Making bound methods with class fields
|
### Making bound methods with class fields
|
||||||
|
|
||||||
As demonstrated in the chapter <info:bind> functions in JavaScript have a dynamic `this`. It depends on the context of the call.
|
As demonstrated in the chapter <info:bind> functions in JavaScript have a dynamic `this`. It depends on the context of the call.
|
||||||
|
@ -375,30 +376,9 @@ The problem is called "losing `this`".
|
||||||
There are two approaches to fixing it, as discussed in the chapter <info:bind>:
|
There are two approaches to fixing it, as discussed in the chapter <info:bind>:
|
||||||
|
|
||||||
1. Pass a wrapper-function, such as `setTimeout(() => button.click(), 1000)`.
|
1. Pass a wrapper-function, such as `setTimeout(() => button.click(), 1000)`.
|
||||||
2. Bind the method to object, e.g. in the constructor:
|
2. Bind the method to object, e.g. in the constructor.
|
||||||
|
|
||||||
```js run
|
Class fields provide another, quite elegant syntax:
|
||||||
class Button {
|
|
||||||
constructor(value) {
|
|
||||||
this.value = value;
|
|
||||||
*!*
|
|
||||||
this.click = this.click.bind(this);
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
click() {
|
|
||||||
alert(this.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let button = new Button("hello");
|
|
||||||
|
|
||||||
*!*
|
|
||||||
setTimeout(button.click, 1000); // hello
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Class fields provide a more elegant syntax for the latter solution:
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
class Button {
|
class Button {
|
||||||
|
@ -417,9 +397,9 @@ let button = new Button("hello");
|
||||||
setTimeout(button.click, 1000); // hello
|
setTimeout(button.click, 1000); // hello
|
||||||
```
|
```
|
||||||
|
|
||||||
The class field `click = () => {...}` creates an independent function on each `Button` object, with `this` bound to the object. Then we can pass `button.click` around anywhere, and it will be called with the right `this`.
|
The class field `click = () => {...}` is created on a per-object basis, there's a separate function for each `Button` object, with `this` inside it referencing that object. We can pass `button.click` around anywhere, and the value of `this` will always be correct.
|
||||||
|
|
||||||
That's especially useful in browser environment, when we need to setup a method as an event listener.
|
That's especially useful in browser environment, for event listeners.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
|
|
@ -279,6 +279,102 @@ alert(rabbit.earLength); // 10
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Overriding class fields: a tricky note
|
||||||
|
|
||||||
|
```warn header="Advanced note"
|
||||||
|
This note assumes you have a certain experience with classes, maybe in other programming languages.
|
||||||
|
|
||||||
|
It provides better insight into the language and also explains the behavior that might be a source of bugs (but not very often).
|
||||||
|
|
||||||
|
If you find it difficult to understand, just go on, continue reading, then return to it some time later.
|
||||||
|
```
|
||||||
|
|
||||||
|
We can override not only methods, but also class fields.
|
||||||
|
|
||||||
|
Although, there's a tricky behavior when we access an overridden field in parent constructor, quite different from most other programming languages.
|
||||||
|
|
||||||
|
Consider this example:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class Animal {
|
||||||
|
name = 'animal'
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
alert(this.name); // (*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rabbit extends Animal {
|
||||||
|
name = 'rabbit';
|
||||||
|
}
|
||||||
|
|
||||||
|
new Animal(); // animal
|
||||||
|
*!*
|
||||||
|
new Rabbit(); // animal
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, class `Rabbit` extends `Animal` and overrides `name` field with its own value.
|
||||||
|
|
||||||
|
There's no own constructor in `Rabbit`, so `Animal` constructor is called.
|
||||||
|
|
||||||
|
What's interesting is that in both cases: `new Animal()` and `new Rabbit()`, the `alert` in the line `(*)` shows `animal`.
|
||||||
|
|
||||||
|
**In other words, parent constructor always uses its own field value, not the overridden one.**
|
||||||
|
|
||||||
|
What's odd about it?
|
||||||
|
|
||||||
|
If it's not clear yet, please compare with methods.
|
||||||
|
|
||||||
|
Here's the same code, but instead of `this.name` field we call `this.showName()` method:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
class Animal {
|
||||||
|
showName() { // instead of this.name = 'animal'
|
||||||
|
alert('animal');
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.showName(); // instead of alert(this.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rabbit extends Animal {
|
||||||
|
showName() {
|
||||||
|
alert('rabbit');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Animal(); // animal
|
||||||
|
*!*
|
||||||
|
new Rabbit(); // rabbit
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note: now the output is different.
|
||||||
|
|
||||||
|
And that's what we naturally expect. When the parent constructor is called in the derived class, it uses the overridden method.
|
||||||
|
|
||||||
|
...But for class fields it's not so. As said, the parent constructor always uses the parent field.
|
||||||
|
|
||||||
|
Why is there the difference?
|
||||||
|
|
||||||
|
Well, the reason is in the field initialization order. The class field is initialized:
|
||||||
|
- Before constructor for the base class (that doesn't extend anything),
|
||||||
|
- Imediately after `super()` for the derived class.
|
||||||
|
|
||||||
|
In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As said previously, that's the same as if there was an empty constructor with only `super(...args)`.
|
||||||
|
|
||||||
|
So, `new Rabbit()` calls `super()`, thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no `Rabbit` class fields yet, that's why `Animal` fields are used.
|
||||||
|
|
||||||
|
This subtle difference between fields and methods is specific to JavaScript
|
||||||
|
|
||||||
|
Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what's going on, so we're explaining it here.
|
||||||
|
|
||||||
|
If it becomes a problem, one can fix it by using methods or getters/setters instead of fields.
|
||||||
|
|
||||||
|
|
||||||
## Super: internals, [[HomeObject]]
|
## Super: internals, [[HomeObject]]
|
||||||
|
|
||||||
```warn header="Advanced information"
|
```warn header="Advanced information"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue