refactor classes, add private, minor fixes
|
@ -0,0 +1,46 @@
|
|||
Here's the line with the error:
|
||||
|
||||
```js
|
||||
Rabbit.prototype = Animal.prototype;
|
||||
```
|
||||
|
||||
Here `Rabbit.prototype` and `Animal.prototype` become the same object. So methods of both classes become mixed in that object.
|
||||
|
||||
As a result, `Rabbit.prototype.walk` overwrites `Animal.prototype.walk`, so all animals start to bounce:
|
||||
|
||||
```js run
|
||||
function Animal(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
Animal.prototype.walk = function() {
|
||||
alert(this.name + ' walks');
|
||||
};
|
||||
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
*!*
|
||||
Rabbit.prototype = Animal.prototype;
|
||||
*/!*
|
||||
|
||||
Rabbit.prototype.walk = function() {
|
||||
alert(this.name + " bounces!");
|
||||
};
|
||||
|
||||
*!*
|
||||
let animal = new Animal("pig");
|
||||
animal.walk(); // pig bounces!
|
||||
*/!*
|
||||
```
|
||||
|
||||
The correct variant would be:
|
||||
|
||||
```js
|
||||
Rabbit.prototype.__proto__ = Animal.prototype;
|
||||
// or like this:
|
||||
Rabbit.prototype = Object.create(Animal.prototype);
|
||||
```
|
||||
|
||||
That makes prototypes separate, each of them stores methods of the corresponding class, but `Rabbit.prototype` inherits from `Animal.prototype`.
|
|
@ -0,0 +1,29 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# An error in the inheritance
|
||||
|
||||
Find an error in the prototypal inheritance below.
|
||||
|
||||
What's wrong? What are consequences going to be?
|
||||
|
||||
```js
|
||||
function Animal(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
Animal.prototype.walk = function() {
|
||||
alert(this.name + ' walks');
|
||||
};
|
||||
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
Rabbit.prototype = Animal.prototype;
|
||||
|
||||
Rabbit.prototype.walk = function() {
|
||||
alert(this.name + " bounces!");
|
||||
};
|
||||
```
|
|
@ -0,0 +1 @@
|
|||
Please note that properties that were internal in functional style (`template`, `timer`) and the internal method `render` are marked private with the underscore `_`.
|
|
@ -0,0 +1,32 @@
|
|||
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) mins = '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,34 @@
|
|||
function Clock({ template }) {
|
||||
|
||||
let timer;
|
||||
|
||||
function render() {
|
||||
let date = new Date();
|
||||
|
||||
let hours = date.getHours();
|
||||
if (hours < 10) hours = '0' + hours;
|
||||
|
||||
let mins = date.getMinutes();
|
||||
if (mins < 10) mins = '0' + mins;
|
||||
|
||||
let secs = date.getSeconds();
|
||||
if (secs < 10) secs = '0' + secs;
|
||||
|
||||
let output = template
|
||||
.replace('h', hours)
|
||||
.replace('m', mins)
|
||||
.replace('s', secs);
|
||||
|
||||
console.log(output);
|
||||
}
|
||||
|
||||
this.stop = function() {
|
||||
clearInterval(timer);
|
||||
};
|
||||
|
||||
this.start = function() {
|
||||
render();
|
||||
timer = setInterval(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 prototypes
|
||||
|
||||
The `Clock` class is written in functional style. Rewrite it using prototypes.
|
||||
|
||||
P.S. The clock ticks in the console, open it to see.
|
240
1-js/09-classes/01-class-patterns/article.md
Normal file
|
@ -0,0 +1,240 @@
|
|||
|
||||
# Class patterns
|
||||
|
||||
```quote author="Wikipedia"
|
||||
In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).
|
||||
```
|
||||
|
||||
There's a special syntax construct and a keyword `class` in JavaScript. But before studying it, we should consider that the term "class" comes from the theory of object-oriented programming. The definition is cited above, and it's language-independent.
|
||||
|
||||
In JavaScript there are several well-known programming patterns to make classes even without using the `class` keyword. People talk about "classes" meaning no only those defined with `class`, but also with these patterns.
|
||||
|
||||
The `class` construct will be described in the next chapter, but in JavaScript it's a "syntax sugar" and an extension of the prototypal class pattern described here.
|
||||
|
||||
|
||||
## Functional class pattern
|
||||
|
||||
The constructor function below can be considered a "class" according to the definition:
|
||||
|
||||
```js run
|
||||
function User(name) {
|
||||
this.sayHi = function() {
|
||||
alert(name);
|
||||
};
|
||||
}
|
||||
|
||||
let user = new User("John");
|
||||
user.sayHi(); // John
|
||||
```
|
||||
|
||||
It follows all parts of the definition:
|
||||
|
||||
1. It is a "program-code-template" for creating objects (callable with `new`).
|
||||
2. It provides initial values for the state (`name` from parameters).
|
||||
3. It provides methods (`sayHi`).
|
||||
|
||||
This is called *functional class pattern*.
|
||||
|
||||
In the functional class pattern, local variables and nested functions inside `User`, that are not assigned to `this`, are visible from inside, but not accessible by the outer code.
|
||||
|
||||
So we can easily add internal functions and variables, like `calcAge()` here:
|
||||
|
||||
```js run
|
||||
function User(name, birthday) {
|
||||
*!*
|
||||
// only visible from other methods inside User
|
||||
function calcAge() {
|
||||
return new Date().getFullYear() - birthday.getFullYear();
|
||||
}
|
||||
*/!*
|
||||
|
||||
this.sayHi = function() {
|
||||
alert(`${name}, age:${calcAge()}`);
|
||||
};
|
||||
}
|
||||
|
||||
let user = new User("John", new Date(2000, 0, 1));
|
||||
user.sayHi(); // John, age:17
|
||||
```
|
||||
|
||||
In this code variables `name`, `birthday` and the function `calcAge()` are internal, *private* to the object. They are only visible from inside of it.
|
||||
|
||||
On the other hand, `sayHi` is the external, *public* method. The external code that creates `user` can access it.
|
||||
|
||||
This way we can hide internal implementation details and helper methods from the outer code. Only what's assigned to `this` becomes visible outside.
|
||||
|
||||
## Factory class pattern
|
||||
|
||||
We can create a class without using `new` at all.
|
||||
|
||||
Like this:
|
||||
|
||||
```js run
|
||||
function User(name, birthday) {
|
||||
// only visible from other methods inside User
|
||||
function calcAge() {
|
||||
return new Date().getFullYear() - birthday.getFullYear();
|
||||
}
|
||||
|
||||
return {
|
||||
sayHi() {
|
||||
alert(`${name}, age:${calcAge()}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
*!*
|
||||
let user = User("John", new Date(2000, 0, 1));
|
||||
*/!*
|
||||
user.sayHi(); // John, age:17
|
||||
```
|
||||
|
||||
As we can see, the function `User` returns an object with public properties and methods. The only benefit of this method is that we can omit `new`: write `let user = User(...)` instead of `let user = new User(...)`. In other aspects it's almost the same as the functional pattern.
|
||||
|
||||
## Prototype-based classes
|
||||
|
||||
Prototype-based classes are the most important and generally the best. Functional and factory class patterns are rarely used in practice.
|
||||
|
||||
Soon you'll see why.
|
||||
|
||||
Here's the same class rewritten using prototypes:
|
||||
|
||||
```js run
|
||||
function User(name, birthday) {
|
||||
*!*
|
||||
this._name = name;
|
||||
this._birthday = birthday;
|
||||
*/!*
|
||||
}
|
||||
|
||||
*!*
|
||||
User.prototype._calcAge = function() {
|
||||
*/!*
|
||||
return new Date().getFullYear() - this._birthday.getFullYear();
|
||||
};
|
||||
|
||||
User.prototype.sayHi = function() {
|
||||
alert(`${this._name}, age:${this._calcAge()}`);
|
||||
};
|
||||
|
||||
let user = new User("John", new Date(2000, 0, 1));
|
||||
user.sayHi(); // John, age:17
|
||||
```
|
||||
|
||||
The code structure:
|
||||
|
||||
- The constructor `User` only initializes the current object state.
|
||||
- Methods are added to `User.prototype`.
|
||||
|
||||
As we can see, methods are lexically not inside `function User`, they do not share a common lexical environment. If we declare variables inside `function User`, then they won't be visible to methods.
|
||||
|
||||
So, there is a widely known agreement that internal properties and methods are prepended with an underscore `"_"`. Like `_name` or `_calcAge()`. Technically, that's just an agreement, the outer code still can access them. But most developers recognize the meaning of `"_"` and try not to touch prefixed properties and methods in the external code.
|
||||
|
||||
Here are the advantages over the functional pattern:
|
||||
|
||||
- In the functional pattern, each object has its own copy of every method. We assign a separate copy of `this.sayHi = function() {...}` and other methods in the constructor.
|
||||
- In the prototypal pattern, all methods are in `User.prototype` that is shared between all user objects. An object itself only stores the data.
|
||||
|
||||
So the prototypal pattern is more memory-efficient.
|
||||
|
||||
...But not only that. Prototypes allow us to setup the inheritance in a really efficient way. Built-in JavaScript objects all use prototypes. Also there's a special syntax construct: "class" that provides nice-looking syntax for them. And there's more, so let's go on with them.
|
||||
|
||||
## Prototype-based inheritance for classes
|
||||
|
||||
Let's say we have two prototype-based classes.
|
||||
|
||||
`Rabbit`:
|
||||
|
||||
```js
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
Rabbit.prototype.jump = function() {
|
||||
alert(`${this.name} jumps!`);
|
||||
};
|
||||
|
||||
let rabbit = new Rabbit("My rabbit");
|
||||
```
|
||||
|
||||

|
||||
|
||||
...And `Animal`:
|
||||
|
||||
```js
|
||||
function Animal(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
Animal.prototype.eat = function() {
|
||||
alert(`${this.name} eats.`);
|
||||
};
|
||||
|
||||
let animal = new Animal("My animal");
|
||||
```
|
||||
|
||||

|
||||
|
||||
Right now they are fully independent.
|
||||
|
||||
But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods.
|
||||
|
||||
What does it mean in the language of prototypes?
|
||||
|
||||
Right now methods for `rabbit` objects are in `Rabbit.prototype`. We'd like `rabbit` to use `Animal.prototype` as a "fallback", if the method is not found in `Rabbit.prototype`.
|
||||
|
||||
So the prototype chain should be `rabbit` -> `Rabbit.prototype` -> `Animal.prototype`.
|
||||
|
||||
Like this:
|
||||
|
||||

|
||||
|
||||
The code to implement that:
|
||||
|
||||
```js run
|
||||
// Same Animal as before
|
||||
function Animal(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
// All animals can eat, right?
|
||||
Animal.prototype.eat = function() {
|
||||
alert(`${this.name} eats.`);
|
||||
};
|
||||
|
||||
// Same Rabbit as before
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
Rabbit.prototype.jump = function() {
|
||||
alert(`${this.name} jumps!`);
|
||||
};
|
||||
|
||||
*!*
|
||||
// setup the inheritance chain
|
||||
Rabbit.prototype.__proto__ = Animal.prototype; // (*)
|
||||
*/!*
|
||||
|
||||
let rabbit = new Rabbit("White Rabbit");
|
||||
*!*
|
||||
rabbit.eat(); // rabbits can eat too
|
||||
*/!*
|
||||
rabbit.jump();
|
||||
```
|
||||
|
||||
The line `(*)` sets up the prototype chain. So that `rabbit` first searches methods in `Rabbit.prototype`, then `Animal.prototype`. And then, just for completeness, let's mention that if the method is not found in `Animal.prototype`, then the search continues in `Object.prototype`, because `Animal.prototype` is a regular plain object, so it inherits from it.
|
||||
|
||||
So here's the full picture:
|
||||
|
||||

|
||||
|
||||
## Summary
|
||||
|
||||
The term "class" comes from the object-oriented programming. In JavaScript it usually means the functional class pattern or the prototypal pattern. The prototypal pattern is more powerful and memory-efficient, so it's recommended to stick to it.
|
||||
|
||||
According to the prototypal pattern:
|
||||
1. Methods are stored in `Class.prototype`.
|
||||
2. Prototypes inherit from each other.
|
||||
|
||||
In the next chapter we'll study `class` keyword and construct. It allows to write prototypal classes shorter and provides some additional benefits.
|
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 37 KiB |