refactor classes, add private, minor fixes
This commit is contained in:
parent
a0c07342ad
commit
1373f6158c
270 changed files with 1513 additions and 890 deletions
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
226
1-js/09-classes/04-static-properties-methods/article.md
Normal file
226
1-js/09-classes/04-static-properties-methods/article.md
Normal file
|
@ -0,0 +1,226 @@
|
|||
|
||||
# Static properties and methods
|
||||
|
||||
We can also assign a methods to the class function, not to its `"prototype"`. Such methods are called *static*.
|
||||
|
||||
An example:
|
||||
|
||||
```js run
|
||||
class User {
|
||||
*!*
|
||||
static staticMethod() {
|
||||
*/!*
|
||||
alert(this === User);
|
||||
}
|
||||
}
|
||||
|
||||
User.staticMethod(); // true
|
||||
```
|
||||
|
||||
That actually does the same as assigning it as a function property:
|
||||
|
||||
```js
|
||||
function User() { }
|
||||
|
||||
User.staticMethod = function() {
|
||||
alert(this === User);
|
||||
};
|
||||
```
|
||||
|
||||
The value of `this` inside `User.staticMethod()` 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:
|
||||
|
||||
```js run
|
||||
class Article {
|
||||
constructor(title, date) {
|
||||
this.title = title;
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
*!*
|
||||
static compare(articleA, articleB) {
|
||||
return articleA.date - articleB.date;
|
||||
}
|
||||
*/!*
|
||||
}
|
||||
|
||||
// usage
|
||||
let articles = [
|
||||
new Article("Mind", new Date(2019, 1, 1)),
|
||||
new Article("Body", new Date(2019, 0, 1)),
|
||||
new Article("JavaScript", new Date(2019, 11, 1))
|
||||
];
|
||||
|
||||
*!*
|
||||
articles.sort(Article.compare);
|
||||
*/!*
|
||||
|
||||
alert( articles[0].title ); // Body
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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. ...
|
||||
|
||||
The first way can be implemented by the constructor. And for the second one we can make a static method of the class.
|
||||
|
||||
Like `Article.createTodays()` here:
|
||||
|
||||
```js run
|
||||
class Article {
|
||||
constructor(title, date) {
|
||||
this.title = title;
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
*!*
|
||||
static createTodays() {
|
||||
// remember, this = Article
|
||||
return new this("Today's digest", new Date());
|
||||
}
|
||||
*/!*
|
||||
}
|
||||
|
||||
let article = Article.createTodays();
|
||||
|
||||
alert( article.title ); // Todays digest
|
||||
```
|
||||
|
||||
Now every time we need to create a today's digest, we can call `Article.createTodays()`. Once again, that's not a method of an article, but a method of the whole class.
|
||||
|
||||
Static methods are also used in database-related classes to search/save/remove entries from the database, like this:
|
||||
|
||||
```js
|
||||
// assuming Article is a special class for managing articles
|
||||
// static method to remove the article:
|
||||
Article.remove({id: 12345});
|
||||
```
|
||||
|
||||
## Static properties
|
||||
|
||||
[recent browser=Chrome]
|
||||
|
||||
Static properties are also possible, just like regular class properties:
|
||||
|
||||
```js run
|
||||
class Article {
|
||||
static publisher = "Ilya Kantor";
|
||||
}
|
||||
|
||||
alert( Article.publisher ); // Ilya Kantor
|
||||
```
|
||||
|
||||
That is the same as a direct assignment to `Article`:
|
||||
|
||||
```js
|
||||
Article.publisher = "Ilya Kantor";
|
||||
```
|
||||
|
||||
## Statics and inheritance
|
||||
|
||||
Statics are inhereted, we can access `Parent.method` as `Child.method`.
|
||||
|
||||
For instance, `Animal.compare` in the code below is inhereted and accessible as `Rabbit.compare`:
|
||||
|
||||
```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 properties 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`.
|
||||
|
||||
## 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 properties are used when we'd like to store class-level data, also not bound to an instance.
|
||||
|
||||
The syntax is:
|
||||
|
||||
```js
|
||||
class MyClass {
|
||||
static property = ...;
|
||||
|
||||
static method() {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That's technically the same as assigning to the class itself:
|
||||
|
||||
```js
|
||||
MyClass.property = ...
|
||||
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`.
|
Loading…
Add table
Add a link
Reference in a new issue