Skip to content

Commit

Permalink
minor
Browse files Browse the repository at this point in the history
  • Loading branch information
iliakan committed Aug 4, 2019
1 parent 800d47c commit f968724
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 35 deletions.
34 changes: 23 additions & 11 deletions 1-js/09-classes/02-class-inheritance/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Animal {
}
stop() {
this.speed = 0;
alert(`${this.name} stopped.`);
alert(`${this.name} stands still.`);
}
}

Expand Down Expand Up @@ -65,7 +65,7 @@ class Animal {
}
stop() {
this.speed = 0;
alert(`${this.name} stopped.`);
alert(`${this.name} stands still.`);
}
}

Expand Down Expand Up @@ -155,7 +155,7 @@ class Animal {
stop() {
this.speed = 0;
alert(`${this.name} stopped.`);
alert(`${this.name} stands still.`);
}
}
Expand All @@ -176,7 +176,7 @@ class Rabbit extends Animal {
let rabbit = new Rabbit("White Rabbit");
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.
Expand Down Expand Up @@ -265,12 +265,12 @@ In JavaScript, there's a distinction between a "constructor function of an inher

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.
- 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 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
class Animal {
Expand Down Expand Up @@ -306,16 +306,24 @@ alert(rabbit.earLength); // 10

## 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.

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.

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__`:

```js run
Expand Down Expand Up @@ -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.

Here's the demo of a wrong `super` call:
Here's the demo of a wrong `super` result after copying:
```js run
let animal = {
Expand All @@ -468,6 +476,7 @@ let animal = {
}
};
// rabbit inherits from animal
let rabbit = {
__proto__: animal,
sayHi() {
Expand All @@ -481,6 +490,7 @@ let plant = {
}
};
// tree inherits from plant
let tree = {
__proto__: plant,
*!*
Expand All @@ -497,9 +507,11 @@ A call to `tree.sayHi()` shows "I'm an animal". Definitevely wrong.

The reason is simple:
- 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`.
Here's the diagram of what happens:

![](super-homeobject-wrong.svg)

### Methods, not function properties
Expand Down
51 changes: 27 additions & 24 deletions 1-js/09-classes/03-static-properties-methods/article.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

# 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
class User {
Expand All @@ -17,7 +17,7 @@ class User {
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
class User() { }
Expand All @@ -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.

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
class Article {
Expand Down Expand Up @@ -61,13 +61,13 @@ articles.sort(Article.compare);
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:

1. Create by given parameters (`title`, `date` etc).
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.

Expand Down Expand Up @@ -107,7 +107,7 @@ Article.remove({id: 12345});

[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
class Article {
Expand All @@ -123,9 +123,9 @@ That is the same as a direct assignment to `Article`:
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`:

Expand Down Expand Up @@ -169,36 +169,39 @@ 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.
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`.


![](animal-rabbit-static.svg)

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:

1. `Rabbit` function prototypally inherits from `Animal` function.
2. `Rabbit.prototype` prototypally inherits from `Animal.prototype`.

Here, let's check that:
As the result, inheritance works both for regular and static methods.

Here, let's check that by code:

```js run
class Animal {}
class Rabbit extends Animal {}

// for static properties and methods
// for statics
alert(Rabbit.__proto__ === Animal); // true

// the next step up leads to Function.prototype
alert(Animal.__proto__ === Function.prototype); // true

// the "normal" prototype chain for object methods
// for regular methods
alert(Rabbit.prototype.__proto__ === Animal.prototype);
```

This way `Rabbit` has access to all static methods of `Animal`.

## 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.

Expand All @@ -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
MyClass.property = ...
Expand All @@ -223,4 +226,4 @@ MyClass.method = ...

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`.

0 comments on commit f968724

Please sign in to comment.