ok
|
@ -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,18 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Console clock</title>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script src="clock.js"></script>
|
||||
<script>
|
||||
let clock = new Clock({template: 'h:m:s'});
|
||||
clock.start();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
|
||||
function Clock({ template }) {
|
||||
this._template = template;
|
||||
}
|
||||
|
||||
Clock.prototype._render = function() {
|
||||
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);
|
||||
};
|
||||
|
||||
Clock.prototype.stop = function() {
|
||||
clearInterval(this._timer);
|
||||
};
|
||||
|
||||
Clock.prototype.start = function() {
|
||||
this._render();
|
||||
this._timer = setInterval(() => this._render(), 1000);
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Console clock</title>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script src="clock.js"></script>
|
||||
<script>
|
||||
let clock = new Clock({template: 'h:m:s'});
|
||||
clock.start();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,9 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Rewrite to class
|
||||
|
||||
Rewrite the `Clock` class from prototypes to the modern "class" syntax.
|
||||
|
||||
P.S. The clock ticks in the console, open it to see.
|
BIN
1-js/9-object-inheritance/09-class/animal-rabbit-extends.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
1-js/9-object-inheritance/09-class/animal-rabbit-extends@2x.png
Normal file
After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 66 KiB |
|
@ -1,21 +1,17 @@
|
|||
|
||||
# Classes
|
||||
|
||||
The "class" construct allows to define prototype-based classes with a much more comfortable syntax than before.
|
||||
|
||||
But more than that -- there are also other inheritance-related features baked in.
|
||||
The "class" construct allows to define prototype-based classes with a clean, nice-looking syntax.
|
||||
|
||||
[cut]
|
||||
|
||||
## The "class" syntax
|
||||
|
||||
<!--The class syntax is versatile, so we'll first see the overall picture and then explore it by examples.-->
|
||||
The `class` syntax is versatile, so we'll start from a simple example first.
|
||||
|
||||
The class syntax is versatile, so we'll start from a simple class, and then build on top of it.
|
||||
Here's a prototype-based class `User`:
|
||||
|
||||
A prototype-based class `User`:
|
||||
|
||||
```js
|
||||
```js run
|
||||
function User(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
@ -23,13 +19,16 @@ function User(name) {
|
|||
User.prototype.sayHi = function() {
|
||||
alert(this.name);
|
||||
}
|
||||
|
||||
let user = new User("John");
|
||||
user.sayHi();
|
||||
```
|
||||
|
||||
Can be rewritten as:
|
||||
...And that's the same using `class` syntax:
|
||||
|
||||
```js
|
||||
```js run
|
||||
class User {
|
||||
|
||||
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
@ -39,15 +38,19 @@ class User {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
let user = new User("John");
|
||||
user.sayHi();
|
||||
```
|
||||
|
||||
The `"class"` construct is tricky from the beginning. We may think that it defines a new lanuage-level entity, but no!
|
||||
It's easy to see that the two examples are alike. So, what exactly the `class` does? We may think that it defines a new language-level entity, but that would be wrong.
|
||||
|
||||
The resulting variable `User` is actually a function labelled as a `"constructor"`. The value of `User.prototype` is an object with methods listed in the definition. Here it includes `sayHi` and, well, the reference to `constructor` also.
|
||||
The `class User {...}` here actually does two things:
|
||||
|
||||

|
||||
1. Declares a variable `User` that references the function named `"constructor"`.
|
||||
2. Puts into `User.prototype` methods listed in the definition. Here it includes `sayHi` and the `constructor`.
|
||||
|
||||
Here, let's check it:
|
||||
Here's some code to demonstrate that:
|
||||
|
||||
```js run
|
||||
class User {
|
||||
|
@ -55,29 +58,60 @@ class User {
|
|||
sayHi() { alert(this.name); }
|
||||
}
|
||||
|
||||
// proof: User is the same as constructor
|
||||
*!*
|
||||
// proof: User is the "constructor" function
|
||||
*/!*
|
||||
alert(User == User.prototype.constructor); // true
|
||||
|
||||
// And there are two methods in its "prototype"
|
||||
*!*
|
||||
// proof: there are two methods in its "prototype"
|
||||
*/!*
|
||||
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
|
||||
|
||||
// The usage is same:
|
||||
let user = new User("John");
|
||||
user.sayHi(); // John
|
||||
```
|
||||
|
||||
The class constructor function has two special features:
|
||||
Here's the illustration of `class User`:
|
||||
|
||||
- It can't be called without `new`.
|
||||
- If we output it like `alert(User)`, some engines show `"class User..."`, while others show `"function User..."`. Please don't be confused: the string representation may vary, but that doesn't affect anything.
|
||||

|
||||
|
||||
Please note that no code statements and `property:value` assignments are allowed inside `class`. There may be only methods (without a comma between them) and getters/setters.
|
||||
So `class` is a special syntax to define the constructor with prototype methods.
|
||||
|
||||
Here we use a getter/setter pair for `name` to make sure that it is valid:
|
||||
...But not only that. There are minor tweaks here and there to ensure the right usage.
|
||||
|
||||
For instance, the `constructor` function can't be called without `new`:
|
||||
```js run
|
||||
class User {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
alert(typeof User); // function
|
||||
User(); // Error: Class constructor User cannot be invoked without 'new'
|
||||
```
|
||||
|
||||
```smart header="Outputting a class"
|
||||
If we output it like `alert(User)`, some engines show `"class User..."`, while others show `"function User..."`.
|
||||
|
||||
Please don't be confused: the string representation may vary, but that's still a function, there is no separate "class" entity in Javascript language.
|
||||
```
|
||||
|
||||
```smart header="Class methods are non-enumerable"
|
||||
Class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. That's good, because if we `for..in` over an object, we usually don't want its class methods.
|
||||
```
|
||||
|
||||
```smart header="What if there's no constructor?"
|
||||
If there's no `constructor` in the `class` construct, then an empty function is generated, same as if write `constructor() {}`.
|
||||
```
|
||||
|
||||
```smart header="Classes always `use strict`"
|
||||
All code inside the class construct is automatically in the strict mode.
|
||||
```
|
||||
|
||||
### Getters/setters
|
||||
|
||||
Classes may also include getters/setters. Here's an example with `user.name` implemented using them:
|
||||
|
||||
```js run
|
||||
class User {
|
||||
|
||||
|
||||
constructor(name) {
|
||||
// invokes the setter
|
||||
this.name = name;
|
||||
|
@ -93,7 +127,8 @@ class User {
|
|||
set name(value) {
|
||||
*/!*
|
||||
if (value.length < 4) {
|
||||
throw new Error("Name too short.");
|
||||
alert("Name too short.");
|
||||
return;
|
||||
}
|
||||
this._name = value;
|
||||
}
|
||||
|
@ -103,44 +138,66 @@ class User {
|
|||
let user = new User("John");
|
||||
alert(user.name); // John
|
||||
|
||||
user = new User(""); // Error: name too short
|
||||
user = new User(""); // Name too short.
|
||||
```
|
||||
|
||||
```smart header="Class methods are non-enumerable"
|
||||
Class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. That's good, because if we `for..in` over an object, we usually don't want its methods.
|
||||
### Only methods
|
||||
|
||||
Unlike object literals, no `property:value` assignments are allowed inside `class`. There may be only methods (without a comma between them) and getters/setters.
|
||||
|
||||
The idea is that everything inside `class` goes to the prototype. And the prototype should store methods only, which are shared between objects. The data describing a concrete object state should reside in individual objects.
|
||||
|
||||
If we really insist on putting a non-function value into the prototype, then `class` can't help here. We can alter `prototype` manually though, like this:
|
||||
|
||||
```js run
|
||||
class User { }
|
||||
|
||||
User.prototype.test = 5;
|
||||
|
||||
alert( new User().test ); // 5
|
||||
```
|
||||
|
||||
```smart header="What if there's no constructor?"
|
||||
If there's no `constructor` in the `class` construct, then an empty function is generated, same as `constructor() {}`. So things still work the same way.
|
||||
So, technically that's possible, but we should know why we're doing it.
|
||||
|
||||
An alternative here would be to use a getter:
|
||||
|
||||
```js run
|
||||
class User {
|
||||
get test() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
alert( new User().test ); // 5
|
||||
```
|
||||
|
||||
```smart header="Classes always `use strict`"
|
||||
All code inside the class construct is automatically in the strict mode.
|
||||
```
|
||||
From the external code, the usage is the same. But the getter variant is probably a bit slower.
|
||||
|
||||
## Class Expression
|
||||
|
||||
Just like functions, classes can be defined inside any other expression, passed around, returned from functions etc:
|
||||
Just like functions, classes can be defined inside another expression, passed around, returned etc.
|
||||
|
||||
Here's a class-returning function ("class factory"):
|
||||
|
||||
```js run
|
||||
function getClass() {
|
||||
function getClass(phrase) {
|
||||
*!*
|
||||
return class {
|
||||
sayHi() {
|
||||
alert("Hello");
|
||||
sayHi() {
|
||||
alert(phrase);
|
||||
};
|
||||
};
|
||||
*/!*
|
||||
}
|
||||
|
||||
let User = getClass();
|
||||
let User = getClass("Hello");
|
||||
|
||||
new User().sayHi(); // Hello
|
||||
```
|
||||
|
||||
That's normal if we recall that `class` is just a special form of constructor-and-prototype definition.
|
||||
That's quite normal if we recall that `class` is just a special form of function-with-prototype definition.
|
||||
|
||||
Such classes also may have a name, that is visible inside that class only:
|
||||
And, like Named Function Expressions, such classes also may have a name, that is visible inside that class only:
|
||||
|
||||
```js run
|
||||
let User = class *!*MyClass*/!* {
|
||||
|
@ -149,342 +206,11 @@ let User = class *!*MyClass*/!* {
|
|||
}
|
||||
};
|
||||
|
||||
new User().sayHi(); // works
|
||||
new User().sayHi(); // works, shows MyClass definition
|
||||
|
||||
alert(MyClass); // error, MyClass is only visible in methods of the class
|
||||
```
|
||||
|
||||
|
||||
## Inheritance, super
|
||||
|
||||
To inherit from another class, we can specify `"extends"` and the parent class before the brackets `{..}`.
|
||||
|
||||
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 adds a `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`, just as you expect it to be, and as we've seen before.
|
||||
|
||||
Now let's move forward and override a method. Naturally, if we specify our own `stop` in `Rabbit`, then the inherited one will not be called:
|
||||
|
||||
```js
|
||||
class Rabbit extends Animal {
|
||||
stop() {
|
||||
// ...this will be used for rabbit.stop()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
...But usually we don't want to fully replace a parent method, but rather to build on top of it, tweak or extend its functionality. So we do something in our method, but call the parent method before/after 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!
|
||||
```
|
||||
|
||||
With constructors, it is a bit more tricky.
|
||||
|
||||
Let's add a constructor to `Rabbit` that specifies the ear length:
|
||||
|
||||
```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
|
||||
*/!*
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
In JavaScript, there's a distinction between a constructor function of an inheriting class and all others. If there's an `extend`, then the constructor is labelled with an internal property `[[ConstructorKind]]:"derived"`.
|
||||
|
||||
- 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 we have a choice:
|
||||
|
||||
- Either do not specify a constructor in the inheriting class at all. Then it will be created by default as `constructor(...args) { super(...args); }`, so `super` will be called.
|
||||
- Or if we specify it, then we must call `super`, at least to create `this`. The topmost constructor in the inheritance chain is not derived, so it will make it.
|
||||
|
||||
The working variant:
|
||||
|
||||
```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` and learn some interesting things by the way.
|
||||
|
||||
First to say, from all components that we've learned till now, it's impossible for `super` to work.
|
||||
|
||||
Indeed, how it can work? When an object method runs, all it knows is `this`. If `super` wants to take parent methods, maybe it can just use its `[[Prototype]]`?
|
||||
|
||||
Let's try to do it. Without classes, using bare objects at first.
|
||||
|
||||
Here, `rabbit.eat()` should call `animal.eat()`.
|
||||
|
||||
```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 it works.
|
||||
|
||||
|
||||
Now let's add one more object to the chain, and 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
|
||||
this.__proto__.eat.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
let longEar = {
|
||||
__proto__: rabbit,
|
||||
eat() {
|
||||
// do something with long ears and call parent
|
||||
this.__proto__.eat.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
*!*
|
||||
longEar.eat(); // Error: Maximum call stack size exceeded
|
||||
*/!*
|
||||
```
|
||||
|
||||
Doesn't work any more! If we trace `longEar.eat()` call, it becomes obvious, why:
|
||||
|
||||
1. Inside `longEar.eat()`, we pass the call to `rabbit.eat` giving it the same `this=longEar`.
|
||||
2. Inside `rabbit.eat`, we want to pass the call even higher in the chain, but `this=longEar`, so `this.__proto__.eat` ends up being the same `rabbit.eat`!
|
||||
3. ...So it calls itself in the endless loop.
|
||||
|
||||

|
||||
|
||||
There problem seems 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 ascend any further.
|
||||
|
||||
To provide the solution, JavaScript adds one more special 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.
|
||||
|
||||
But `[[HomeObject]]` is used only for calling parent methods, to resolve the prototype. So it doesn't break compatibility.
|
||||
|
||||
Let's see how it works:
|
||||
|
||||
```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.
|
||||
*/!*
|
||||
```
|
||||
|
||||
Okay now, because `super` always resolves the parent relative to the method's `[[HomeObject]]`.
|
||||
|
||||
`[[HomeObject]]` works both in classes and objects. But for objects, methods must be specified exactly the given way: as `method()`, not as `"method: function()"`.
|
||||
|
||||
Here non-method syntax is used, so `[[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 in super, because there's no [[HomeObject]]
|
||||
*/!*
|
||||
```
|
||||
|
||||
## Static methods
|
||||
|
||||
Static methods are bound to the class function, not to its `"prototype"`.
|
||||
|
@ -496,7 +222,7 @@ class User {
|
|||
*!*
|
||||
static staticMethod() {
|
||||
*/!*
|
||||
alert(this == User);
|
||||
alert(this == User);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -505,10 +231,10 @@ User.staticMethod(); // true
|
|||
|
||||
That actually does the same as assigning it as a function property:
|
||||
|
||||
```js
|
||||
```js
|
||||
function User() { }
|
||||
|
||||
User.staticMethod = function() {
|
||||
User.staticMethod = function() {
|
||||
alert(this == User);
|
||||
};
|
||||
```
|
||||
|
@ -549,7 +275,7 @@ alert( articles[0].title ); // Body
|
|||
|
||||
Here `Article.compare` stands "over" the articles, as a meants to compare them.
|
||||
|
||||
Another example would be a so-called "factory" method, that creates an object with specific parameters.
|
||||
Another example would be a so-called "factory" method, that creates an object with specific parameters.
|
||||
|
||||
Like `Article.createTodays()` here:
|
||||
|
||||
|
@ -573,81 +299,12 @@ let article = article.createTodays();
|
|||
alert( articles.title ); // Todays digest
|
||||
```
|
||||
|
||||
Now every time we need to create a todays digest, we can call `Article.createTodays()`.
|
||||
Now every time we need to create a todays digest, we can call `Article.createTodays()`.
|
||||
|
||||
Static methods are often used in database-related classes to search/save/remove entries from the database by a query, without having them at hand.
|
||||
Static methods are often used in database-related classes to search/save/remove entries from the database, like this:
|
||||
|
||||
|
||||
### 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.
|
||||
```js
|
||||
// assuming Article is a special class for managing articles
|
||||
// static method to remove the article:
|
||||
Article.remove({id: 12345});
|
||||
```
|
||||
|
||||
That's actually a very interesting feature, because built-in classes don't behave like that.
|
||||
|
||||
For instance, `Object` has `Object.defineProperty`, `Object.keys` and so on, but other objects do not inherit them.
|
||||
|
||||
Here's the structure for `Date` and `Object`:
|
||||
|
||||

|
||||
|
||||
Both `Object` and `Date` exist independently. Sure, `Date.prototype` inherits from `Object.prototype`, but that's all.
|
||||
|
||||
With classes we have one more arrow:
|
||||
|
||||

|
||||
|
||||
Right, `Rabbit` function now inherits from `Animal` function. And `Animal` function standartly inherits from `Function.prototype` (as other functions do).
|
||||
|
||||
Here, let's check:
|
||||
|
||||
|
||||
```js run
|
||||
class Animal {}
|
||||
class Rabbit extends Animal {}
|
||||
|
||||
alert(Rabbit.__proto__ == Animal); // true
|
||||
|
||||
// and the next step is Function.prototype
|
||||
alert(Animal.__proto__ == Function.prototype); // true
|
||||
```
|
||||
|
||||
This way `Rabbit` has access to all static methods of `Animal`.
|
||||
|
||||
|
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 83 KiB |