This commit is contained in:
Ilya Kantor 2016-11-19 00:13:08 +03:00
parent b1f0cfc5b2
commit 63f55dc65d
137 changed files with 1287 additions and 1651 deletions

View file

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

View file

@ -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!");
};
```

View file

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

View file

@ -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) 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);
};

View file

@ -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>

View file

@ -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) min = '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);
};
}

View file

@ -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>

View file

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

View file

@ -31,41 +31,25 @@ 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 state (`name` from parameters).
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*. It is rarely used, because prototypes are generally better.
Here's the same class rewritten using prototypes:
```js run
function User(name) {
this.name = name;
}
User.prototype.sayHi = function() {
alert(this.name);
};
let user = new User("John");
user.sayHi(); // John
```
Now the method `sayHi` is shared between all users through prototype. That's more memory-efficient as putting a copy of it into every object like the functional pattern does. Prototype-based classes are also more convenient for inheritance. As we've seen, that's what the language itself uses, and we'll be using them further on.
### Internal properties and methods
This is called *functional class pattern*.
In the functional class pattern, variables and functions inside `User`, that are not assigned to `this`, are visible from inside, but not accessible by the outer code.
Here's a bigger example:
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() {
new Date().getFullYear() - birthday.getFullYear();
}
*/!*
this.sayHi = function() {
alert(name + ', age:' + calcAge());
@ -78,13 +62,15 @@ user.sayHi(); // John
Variables `name`, `birthday` and the function `calcAge()` are internal, *private* to the object. They are only visible from inside of it. The external code that creates the `user` only can see a *public* method `sayHi`.
In short, functional classes provide a shared outer lexical environment for private variables and methods.
In works, because functional classes provide a shared lexical environment (of `User`) for private variables and methods.
Prototype-bases classes do not have it. As we can see, methods are created outside of the constructor, in the prototype. And per-object data like `name` is stored in object properties. So, technically they are all available for external code.
## Prototype-based classes
But there is a widely known agreement that internal properties are prepended with an underscore `"_"`.
Functional class pattern is rarely used, because prototypes are generally better.
Like this:
Soon you'll see why.
Here's the same class rewritten using prototypes:
```js run
function User(name, birthday) {
@ -108,17 +94,43 @@ let user = new User("John", new Date(2000,0,1));
user.sayHi(); // John
```
Technically, that changes nothing. But most developers recognize the meaning of `"_"` and try not to touch prefixed properties and methods in external code.
- The constructor `User` only initializes the current object state.
- Methods reside in `User.prototype`.
## Prototype-based classes
Here methods are technically not inside `function User`, so they do not share a common lexical environment.
Prototype-based classes are structured like this:
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.
![](class-inheritance-rabbit-animal.png)
We already can see benefits over the functional pattern:
The code example:
- In the functional pattern, each object has its own copy of methods like `this.sayHi = function() {...}`.
- In the prototypal pattern, there's a common `User.prototype` shared between all user objects.
```js run
So the prototypal pattern is more memory-efficient.
...But not only that. Prototypes allow us to setup the inheritance, precisely the same way as built-in Javascript constructors do. Functional pattern allows to wrap a function into another function, and kind-of emulate inheritance this way, but that's far less effective, so here we won't go into details to save our time.
## Prototype-based inheritance for classes
Let's say we have two prototype-based classes:
```js
function Rabbit(name) {
this.name = name;
}
Rabbit.prototype.jump = function() {
alert(this.name + ' jumps!');
};
let rabbit = new Rabbit("My rabbit");
```
![](rabbit-animal-independent-1.png)
And:
```js
function Animal(name) {
this.name = name;
}
@ -127,31 +139,60 @@ Animal.prototype.eat = function() {
alert(this.name + ' eats.');
};
let animal = new Animal("My animal");
```
![](rabbit-animal-independent-2.png)
Right now they are fully independent.
But naturally we'd like `Rabbit` to inherit from `Animal`. In other words, rabbits should be based on animals, and extend them with methods of their own.
What does it mean in the language on prototypes?
Right now `rabbit` objects have access to `Rabbit.prototype`. We should add `Animal.prototype` to it. So the chain would be `rabbit -> Rabbit.prototype -> Animal.prototype`.
Like this:
![](class-inheritance-rabbit-animal.png)
The code example:
```js run
// Same Animal as before
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
alert(this.name + ' eats.');
};
// Same Rabbit as before
function Rabbit(name) {
this.name = name;
}
*!*
// inherit methods
Object.setPrototypeOf(Rabbit.prototype, Animal.prototype); // (*)
*/!*
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();
rabbit.jump();
```
Here the line `(*)` sets up the prototype chain. So that `rabbit` first searches methods in `Rabbit.prototype`, then `Animal.prototype`. And then `Object.prototype`, because `Animal.prototype` is a regular plain object, so it inherits from it, that's not painted for brevity.
The line `(*)` sets up the prototype chain. So that `rabbit` first searches methods in `Rabbit.prototype`, then `Animal.prototype`. And then, just for completeness, the search may continue in `Object.prototype`, because `Animal.prototype` is a regular plain object, so it inherits from it. But that's not painted for brevity.
The structure of exactly that code piece is:
Here's what the code does:
![](class-inheritance-rabbit-animal-2.png)
## Summary [todo]
## Todo
call parent method (overrides)
One of problems is lots of words, for every method we write "Rabbit.prototype.method = ..." Classes syntax is sugar fixes that.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB