refactor classes, add private, minor fixes

This commit is contained in:
Ilya Kantor 2019-03-05 18:44:28 +03:00
parent a0c07342ad
commit 1373f6158c
270 changed files with 1513 additions and 890 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) 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);
};

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

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

@ -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");
```
![](rabbit-animal-independent-1.png)
...And `Animal`:
```js
function Animal(name) {
this.name = name;
}
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 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:
![](class-inheritance-rabbit-animal.png)
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:
![](class-inheritance-rabbit-animal-2.png)
## 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

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: 37 KiB

View file

@ -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) 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);
}
stop() {
clearInterval(this.timer);
}
start() {
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 }) {
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);
};

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 class
Rewrite the `Clock` class from prototypes to the modern "class" syntax.
P.S. The clock ticks in the console, open it to see.

View file

@ -0,0 +1,272 @@
# Classes
The "class" construct allows to define prototype-based classes with a clean, nice-looking syntax. It also introduces new great features, useful for object-oriented programming.
## The "class" syntax
The `class` syntax is versatile, we'll start with a simple example first.
Here's a prototype-based class `User`:
```js run
function User(name) {
this.name = name;
}
User.prototype.sayHi = function() {
alert(this.name);
}
let user = new User("John");
user.sayHi();
```
...And that's the same using `class` syntax:
```js run
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
let user = new User("John");
user.sayHi();
```
It's easy to see that the two examples are alike. Just please note that methods in a class do not have a comma between them. Novice developers sometimes forget it and put a comma between class methods, and things don't work. That's not a literal object, but a class syntax.
## What is a class?
So, what exactly is a `class`? We may think that it defines a new language-level entity, but that would be wrong.
In Javascript, a class is a kind of a function.
The definition `class User {...}` create such function and puts the methods into `User.prototype`. So the structure is similar.
Here's the code to dig into the class and see that:
```js run
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
*!*
// proof: User is a function
alert(typeof User); // function
*/!*
*!*
// proof: User is the "constructor" function
*/!*
alert(User === User.prototype.constructor); // true
*!*
// proof: there are two methods in its "prototype"
*/!*
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
```
Here's the illustration of what `class User` creates:
![](class-user.png)
So `class` is a special syntax to define a constructor together with its prototype methods. There are also quite a few additional features here and there, we'll study them later on.
## Class Expression
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 makeClass(phrase) {
*!*
// declare a class and return it
return class {
sayHi() {
alert(phrase);
};
};
*/!*
}
let User = makeClass("Hello");
new User().sayHi(); // Hello
```
That's quite normal if we recall that `class` is just a special form of a function-with-prototype definition.
And, like Named Function Expressions, such classes also may have a name, that is visible inside that class only:
```js run
// "Named Class Expression" (alas, no such term, but that's what's going on)
let User = class *!*MyClass*/!* {
sayHi() {
alert(MyClass); // MyClass is visible only inside the class
}
};
new User().sayHi(); // works, shows MyClass definition
alert(MyClass); // error, MyClass not visible outside of the class
```
## Differences of classes vs functions
Classes have some differences compared to regular functions:
Constructors require `new`
: Unlike a regular function, a class `constructor` 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'
```
Different string output
: 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.
Class methods are non-enumerable
: A 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.
Classes have a default `constructor() {}`
: If there's no `constructor` in the `class` construct, then an empty function is generated, same as if we had written `constructor() {}`.
Classes always `use strict`
: All code inside the class construct is automatically in strict mode.
## Getters/setters, other shorthands
Classes also include getters/setters, generators, computed properties etc.
Here's an example for `user.name` implemented using `get/set`:
```js run
class User {
constructor(name) {
// invokes the setter
this.name = name;
}
*!*
get name() {
*/!*
return this._name;
}
*!*
set name(value) {
*/!*
if (value.length < 4) {
alert("Name is too short.");
return;
}
this._name = value;
}
}
let user = new User("John");
alert(user.name); // John
user = new User(""); // Name too short.
```
Internally, getters and setters are created on `User.prototype`, like this:
```js
Object.defineProperties(User.prototype, {
name: {
get() {
return this._name
},
set(name) {
// ...
}
}
});
```
Here's an example with computed properties:
```js run
function f() { return "sayHi"; }
class User {
[f()]() {
alert("Hello");
}
}
new User().sayHi();
```
For a generator method, similarly, prepend it with `*`.
## Class properties
```warn header="Old browsers may need a polyfill"
Class-level properties is a recent addition to the language.
```
In the example before, `User` only had methods. Let's add a property:
```js run
class User {
name = "Anonymous";
sayHi() {
alert(`Hello, ${this.name}!`);
}
}
new User().sayHi();
```
The property is not placed into `User.prototype`. Instead, it is created by `new`, separately for every object. So, the property will never be shared between different objects of the same class.
## Summary
The basic class syntax looks like this:
```js
class MyClass {
prop = value;
constructor(...) {
// ...
}
method(...) {}
get something(...) {}
set something(...) {}
[Symbol.iterator]() {}
// ...
}
```
`MyClass` is technically a function, while methods are written to `MyClass.prototype`.
In the next chapters we'll learn more about classes, including inheritance and other features.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,27 @@
That's because the child constructor must call `super()`.
Here's the corrected code:
```js run
class Animal {
constructor(name) {
this.name = name;
}
}
class Rabbit extends Animal {
constructor(name) {
*!*
super(name);
*/!*
this.created = Date.now();
}
}
*!*
let rabbit = new Rabbit("White Rabbit"); // ok now
*/!*
alert(rabbit.name); // White Rabbit
```

View file

@ -0,0 +1,30 @@
importance: 5
---
# Error creating an instance
Here's the code with `Rabbit` extending `Animal`.
Unfortunately, `Rabbit` objects can't be created. What's wrong? Fix it.
```js run
class Animal {
constructor(name) {
this.name = name;
}
}
class Rabbit extends Animal {
constructor(name) {
this.name = name;
this.created = Date.now();
}
}
*!*
let rabbit = new Rabbit("White Rabbit"); // Error: this is not defined
*/!*
alert(rabbit.name);
```

View file

@ -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) 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);
}
stop() {
clearInterval(this._timer);
}
start() {
this._render();
this._timer = setInterval(() => this._render(), 1000);
}
}

View file

@ -0,0 +1,12 @@
class ExtendedClock extends Clock {
constructor(options) {
super(options);
let { precision=1000 } = options;
this._precision = precision;
}
start() {
this._render();
this._timer = setInterval(() => this._render(), this._precision);
}
};

View file

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Console clock</title>
</head>
<body>
<script src="clock.js"></script>
<script src="extended-clock.js"></script>
<script>
let lowResolutionClock = new ExtendedClock({
template: 'h:m:s',
precision: 10000
});
lowResolutionClock.start();
</script>
</body>
</html>

View file

@ -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) 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);
}
stop() {
clearInterval(this._timer);
}
start() {
this._render();
this._timer = setInterval(() => this._render(), 1000);
}
}

View file

@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Console clock</title>
</head>
<body>
<!-- source clock -->
<script src="clock.js"></script>
<script>
let clock = new Clock({
template: 'h:m:s'
});
clock.start();
/* Your class should work like this: */
/*
let lowResolutionClock = new ExtendedClock({
template: 'h:m:s',
precision: 10000
});
lowResolutionClock.start();
*/
</script>
</body>
</html>

View file

@ -0,0 +1,12 @@
importance: 5
---
# Extended clock
We've got a `Clock` class. As of now, it prints the time every second.
Create a new class `ExtendedClock` that inherits from `Clock` and adds the parameter `precision` -- the number of `ms` between "ticks". Should be `1000` (1 second) by default.
- Your code should be in the file `extended-clock.js`
- Don't modify the original `clock.js`. Extend it.

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -0,0 +1,81 @@
First, let's see why the latter code doesn't work.
The reason becomes obvious if we try to run it. An inheriting class constructor must call `super()`. Otherwise `"this"` won't be "defined".
So here's the fix:
```js run
class Rabbit extends Object {
constructor(name) {
*!*
super(); // need to call the parent constructor when inheriting
*/!*
this.name = name;
}
}
let rabbit = new Rabbit("Rab");
alert( rabbit.hasOwnProperty('name') ); // true
```
But that's not all yet.
Even after the fix, there's still important difference in `"class Rabbit extends Object"` versus `class Rabbit`.
As we know, the "extends" syntax sets up two prototypes:
1. Between `"prototype"` of the constructor functions (for methods).
2. Between the constructor functions itself (for static methods).
In our case, for `class Rabbit extends Object` it means:
```js run
class Rabbit extends Object {}
alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) true
```
So `Rabbit` now provides access to static methods of `Object` via `Rabbit`, like this:
```js run
class Rabbit extends Object {}
*!*
// normally we call Object.getOwnPropertyNames
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b
*/!*
```
But if we don't have `extends Object`, then `Rabbit.__proto__` is not set to `Object`.
Here's the demo:
```js run
class Rabbit {}
alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) false (!)
alert( Rabbit.__proto__ === Function.prototype ); // as any function by default
*!*
// error, no such function in Rabbit
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error
*/!*
```
So `Rabbit` doesn't provide access to static methods of `Object` in that case.
By the way, `Function.prototype` has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`.
Here's the picture:
![](rabbit-extends-object.png)
So, to put it short, there are two differences:
| class Rabbit | class Rabbit extends Object |
|--------------|------------------------------|
| -- | needs to call `super()` in constructor |
| `Rabbit.__proto__ === Function.prototype` | `Rabbit.__proto__ === Object` |

View file

@ -0,0 +1,43 @@
importance: 5
---
# Class extends Object?
As we know, all objects normally inherit from `Object.prototype` and get access to "generic" object methods like `hasOwnProperty` etc.
For instance:
```js run
class Rabbit {
constructor(name) {
this.name = name;
}
}
let rabbit = new Rabbit("Rab");
*!*
// hasOwnProperty method is from Object.prototype
// rabbit.__proto__ === Object.prototype
alert( rabbit.hasOwnProperty('name') ); // true
*/!*
```
But if we spell it out explicitly like `"class Rabbit extends Object"`, then the result would be different from a simple `"class Rabbit"`?
What's the difference?
Here's an example of such code (it doesn't work -- why? fix it?):
```js
class Rabbit extends Object {
constructor(name) {
this.name = name;
}
}
let rabbit = new Rabbit("Rab");
alert( rabbit.hasOwnProperty('name') ); // true
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View file

@ -0,0 +1,431 @@
# Class inheritance
Classes can extend one another. There's a nice syntax, technically based on the prototypal inheritance.
To inherit from another class, we should 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 actually adds a `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`, just as you expect it to be, and as we've seen before.
![](animal-rabbit-extends.png)
So now `rabbit` has access both to its own methods and to methods of `Animal`.
````smart header="Any expression is allowed after `extends`"
Class syntax allows to specify not just a class, but any expression after `extends`.
For instance, a function call that generates the parent class:
```js run
function f(phrase) {
return class {
sayHi() { alert(phrase) }
}
}
*!*
class User extends f("Hello") {}
*/!*
new User().sayHi(); // Hello
```
Here `class User` inherits from the result of `f("Hello")`.
That may be useful for advanced programming patterns when we use functions to generate classes depending on many conditions and can inherit from them.
````
## Overriding a method
Now let's move forward and override a method. As of now, `Rabbit` inherits the `stop` method that sets `this.speed = 0` from `Animal`.
If we specify our own `stop` in `Rabbit`, then it will be used instead:
```js
class Rabbit extends Animal {
stop() {
// ...this will be used for rabbit.stop()
}
}
```
...But usually we don't want to totally replace a parent method, but rather to build on top of it, tweak or extend its functionality. We do something in our method, but call the parent method before/after it 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
this.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!
```
Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the process.
````smart header="Arrow functions have no `super`"
As was mentioned in the chapter <info:arrow-functions>, arrow functions do not have `super`.
If accessed, it's taken from the outer function. For instance:
```js
class Rabbit extends Animal {
stop() {
setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
}
}
```
The `super` in the arrow function is the same as in `stop()`, so it works as intended. If we specified a "regular" function here, there would be an error:
```js
// Unexpected super
setTimeout(function() { super.stop() }, 1000);
```
````
## Overriding constructor
With constructors it gets a little bit tricky.
Till now, `Rabbit` did not have its own `constructor`.
According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following `constructor` is generated:
```js
class Rabbit extends Animal {
// generated for extending classes without own constructors
*!*
constructor(...args) {
super(...args);
}
*/!*
}
```
As we can see, it basically calls the parent `constructor` passing it all the arguments. That happens if we don't write a constructor of our own.
Now let's add a custom constructor to `Rabbit`. It will specify the `earLength` in addition to `name`:
```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: this is not defined.
*/!*
```
Whoops! 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. Let's get into details, so you'd really understand what's going on.
In JavaScript, there's a distinction between a "constructor function of an inheriting class" and all others. In an inheriting class, the corresponding constructor function is labelled with a special internal property `[[ConstructorKind]]:"derived"`.
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.
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.
For `Rabbit` to work, we need to call `super()` before using `this`, like here:
```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`. 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.
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, how to retrieve the `method`? Naturally, we need to take the `method` from the prototype of the current object. How, technically, we (or a JavaScript engine) can do it?
Maybe we can get the method from `[[Prototype]]` of `this`, as `this.__proto__.method`? Unfortunately, that doesn't work.
Let's try to do it. Without classes, using plain objects for the sake of simplicity.
Here, `rabbit.eat()` should call `animal.eat()` method of the parent object:
```js run
let animal = {
name: "Animal",
eat() {
alert(`${this.name} eats.`);
}
};
let rabbit = {
__proto__: animal,
name: "Rabbit",
eat() {
*!*
// that's how super.eat() could presumably work
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 in the code above it actually works as intended: we have the correct `alert`.
Now let's add one more object to the chain. We'll 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 (animal) method
this.__proto__.eat.call(this); // (*)
}
};
let longEar = {
__proto__: rabbit,
eat() {
// ...do something with long ears and call parent (rabbit) method
this.__proto__.eat.call(this); // (**)
}
};
*!*
longEar.eat(); // Error: Maximum call stack size exceeded
*/!*
```
The code doesn't work anymore! We can see the error trying to call `longEar.eat()`.
It may be not that obvious, but if we trace `longEar.eat()` call, then we can see why. In both lines `(*)` and `(**)` the value of `this` is the current object (`longEar`). That's essential: all object methods get the current object as `this`, not a prototype or something.
So, in both lines `(*)` and `(**)` the value of `this.__proto__` is exactly the same: `rabbit`. They both call `rabbit.eat` without going up the chain in the endless loop.
Here's the picture of what happens:
![](this-super-loop.png)
1. Inside `longEar.eat()`, the line `(**)` calls `rabbit.eat` providing it with `this=longEar`.
```js
// inside longEar.eat() we have this = longEar
this.__proto__.eat.call(this) // (**)
// becomes
longEar.__proto__.eat.call(this)
// that is
rabbit.eat.call(this);
```
2. Then in the line `(*)` of `rabbit.eat`, we'd like to pass the call even higher in the chain, but `this=longEar`, so `this.__proto__.eat` is again `rabbit.eat`!
```js
// inside rabbit.eat() we also have this = longEar
this.__proto__.eat.call(this) // (*)
// becomes
longEar.__proto__.eat.call(this)
// or (again)
rabbit.eat.call(this);
```
3. ...So `rabbit.eat` calls itself in the endless loop, because it can't ascend any further.
The problem can't be solved by using `this` alone.
### `[[HomeObject]]`
To provide the solution, JavaScript adds one more special internal 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. So that's a very important change in the language.
But this change is safe. `[[HomeObject]]` is used only for calling parent methods in `super`, to resolve the prototype. So it doesn't break compatibility.
Let's see how it works for `super` -- again, using plain objects:
```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.
*/!*
```
Every method remembers its object in the internal `[[HomeObject]]` property. Then `super` uses it to resolve the parent prototype.
`[[HomeObject]]` is defined for methods defined both in classes and in plain objects. But for objects, methods must be specified exactly the given way: as `method()`, not as `"method: function()"`.
In the example below a non-method syntax is used for comparison. `[[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 calling super (because there's no [[HomeObject]])
*/!*
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View 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`.
![](animal-rabbit-static.png)
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`.

View file

@ -0,0 +1,330 @@
# Private and protected properties and methods
One of the most important principles of object oriented programming -- delimiting internal interface from the external one.
That is "a must" practice in developing anything more complex than a "hello world" app.
To understand this, let's break away from development and turn our eyes into the real world.
Usually, devices that we're using are quite complex. But delimiting the internal interface from the external one allows to use them without problems.
## A real-life example
For instance, a coffee machine. Simple from outside: a button, a display, a few holes...And, surely, the result -- great coffee! :)
![](coffee.jpg)
But inside... (a picture from the repair manual)
![](coffee-inside.jpg)
A lot of details. But we can use it without knowing anything.
Coffee machines are quite reliable, aren't they? We can use one for years, and only if something goes wrong -- bring it for repairs.
The secret of reliability and simplicity of a coffee machine -- all details are well-tuned and *hidden* inside.
If we remove the protective cover from the coffee machine, then using it will be much more complex (where to press?), and dangerous (it can electrocute).
As we'll see, in programming objects are like coffee machines.
But in order to hide inner details, we'll use not a protective cover, but rather special syntax of the language and conventions.
## Internal and external interface
In object-oriented programming, properties and methods are split into two groups:
- *Internal interface* -- methods and properties, accessible from other methods of the class, but not from the outside.
- *External interface* -- methods and properties, accessible also from outside the class.
If we continue the analogy with the coffee machine -- what's hidden inside: a boiler tube, heating element, and so on -- is its internal interface.
An internal interface is used for the object to work, its details use each other. For instance, a boiler tube is attached to the heating element.
But from the outside a coffee machine is closed by the protective cover, so that no one can reach those. Details are hidden and inaccessible. We can use its features via the external interface.
So, all we need to use an object is to know its external interface. We may be completely unaware how it works inside, and that's great.
That was a general introduction.
In JavaScript, there are three types of properties and members:
- Public: accessible from anywhere. They comprise the external interface. Till now we were only using public properties and methods.
- Private: accessible only from inside the class. These are for the internal interface.
In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it. They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to properly do the extension.
Protected fields are not implemented in Javascript on the language level, but in practice they are very convenient, so they are emulated.
In the next step we'll make a coffee machine in Javascript with all these types of properties. A coffee machine has a lot of details, we won't model them to stay simple (though we could).
## Protecting "waterAmount"
Let's make a simple coffee machine class first:
```js run
class CoffeeMachine {
waterAmount = 0; // the amount of water inside
constructor(power) {
this.power = power;
alert( `Created a coffee-machine, power: ${power}` );
}
}
// create the coffee machine
let coffeeMachine = new CoffeeMachine(100);
// add water
coffeeMachine.waterAmount = 200;
```
Right now the properties `waterAmount` and `power` are public. We can easily get/set them from the outside to any value.
Let's change `waterAmount` property to protected to have more control over it. For instance, we don't want anyone to set it below zero.
**Protected properties are usually prefixed with an underscore `_`.**
That is not enforced on the language level, but there's a convention that such properties and methods should not be accessed from the outside. Most programmers follow it.
So our property will be called `_waterAmount`:
```js run
class CoffeeMachine {
_waterAmount = 0;
set waterAmount(value) {
if (value < 0) throw new Error("Negative water");
this._waterAmount = value;
}
get waterAmount() {
return this.waterAmount;
}
constructor(power) {
this._power = power;
}
}
// create the coffee machine
let coffeeMachine = new CoffeeMachine(100);
// add water
coffeeMachine.waterAmount = -10; // Error: Negative water
```
Now the access is under control, so setting the water below zero fails.
## Read-only "power"
For `power` property, let's make it read-only. It sometimes happens that a property must be set at creation time only, and then never modified.
That's exactly the case for a coffee machine: power never changes.
To do so, we only need to make getter, but not the setter:
```js run
class CoffeeMachine {
// ...
constructor(power) {
this._power = power;
}
get power() {
return this._power;
}
}
// create the coffee machine
let coffeeMachine = new CoffeeMachine(100);
alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W
coffeeMachine.power = 25; // Error (no setter)
```
````smart header="Getter/setter functions"
Here we used getter/setter syntax.
But most of the time `get.../set...` functions are preferred, like this:
```js
class CoffeeMachine {
_waterAmount = 0;
*!*setWaterAmount(value)*/!* {
if (value < 0) throw new Error("Negative water");
this._waterAmount = value;
}
*!*getWaterAmount()*/!* {
return this.waterAmount;
}
}
new CoffeeMachine().setWaterAmount(100);
```
That looks a bit longer, but functions are more flexible. They can accept multiple arguments (even if we don't need them right now). So, for the future, just in case we need to refactor something, functions are a safer choise.
Surely, there's a tradeoff. On the other hand, get/set syntax is shorter, so ultimately there's no strict rule, it's up to you to decide.
````
```smart header="Protected fields are inherited"
If we inherit `class MegaMachine extends CoffeeMachine`, then nothing prevents us from accessing `this._waterAmount` or `this._power` from the methods of the new class.
So protected fields are naturally inheritable. Unlike private ones that we'll see below.
```
## Private "#waterLimit"
[recent browser=none]
There's a finished Javascript proposal, almost in the standard, that provides language-level support for private properties and methods.
Privates should start with `#`. They are only accessible from inside the class.
For instance, here we add a private `#waterLimit` property and extract the water-checking logic into a separate method:
```js
class CoffeeMachine {
*!*
#waterLimit = 200;
*/!*
*!*
#checkWater(water) {
if (value < 0) throw new Error("Negative water");
if (value > this.#waterLimit) throw new Error("Too much water");
}
*/!*
_waterAmount = 0;
set waterAmount(value) {
*!*
this.#checkWater(value);
*/!*
this._waterAmount = value;
}
get waterAmount() {
return this.waterAmount;
}
}
let coffeeMachine = new CoffeeMachine();
*!*
coffeeMachine.#checkWater(); // Error
coffeeMachine.#waterLimit = 1000; // Error
*/!*
coffeeMachine.waterAmount = 100; // Works
```
On the language level, `#` is a special sign that the field is private. We can't access it from outside or from inhereting classes.
Private fields do not conflict with public ones. We can have both private `#waterAmount` and public `waterAmount` fields at the same time.
For instance, let's make `waterAmount` an accessor for `#waterAmount`:
```js run
class CoffeeMachine {
#waterAmount = 0;
get waterAmount() {
return this.#waterAmount;
}
set waterAmount(value) {
if (value < 0) throw new Error("Negative water");
this.#waterAmount = value;
}
}
let machine = new CoffeeMachine();
machine.waterAmount = 100;
alert(machine.#waterAmount); // Error
```
Unlike protected ones, private fields are enforced by the language itselfs. That's a good thing.
But if we inherit from `CoffeeMachine`, then we'll have no direct access to `#waterAmount`. We'll need to rely on `waterAmount` getter/setter:
```js
class CoffeeMachine extends CoffeeMachine() {
method() {
*!*
alert( this.#waterAmount ); // Error: can only access from CoffeeMachine
*/!*
}
}
```
In many scenarios such limitation is too severe. If we extend a `CoffeeMachine`, we may have legitimate reason to access its internals. That's why protected fields are used most of the time, even though they are not supported by the language syntax.
````warn
Private fields are special.
Remember, usually we can access fields by this[name]:
```js
class User {
...
sayHi() {
let fieldName = "name";
alert(`Hello, ${this[fieldName]}`);
}
}
```
With private fields that's impossible: `this['#name']` doesn't work. That's a syntax limitation to ensure privacy.
````
## Summary
In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation]("https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)").
It gives the following benefits:
Protection for users, so that they don't shoot themselves in the feet
: Imagine, there's a team of developers using a coffee machine. It was made by the "Best CoffeeMachine" company, and works fine, but a protective cover was removed. So the internal interface is exposed.
All developers are civilized -- they use the coffee machine as intended. But one of them, John, decided that he's the smartest one, and made some tweaks in the coffee machine internals. So the coffee machine failed two days later.
That's surely not John's fault, but rather the person who removed the protective cover and let John do his manipulations.
The same in programming. If a user of a class will change things not intended to be changed from the outside -- the consequences are unpredictable.
Supportable
: The situation in programming is more complex than with a real-life coffee machine, because we don't just buy it once. The code constantly undergoes development and improvement.
**If we strictly delimit the internal interface, then the developer of the class can freely change its internal properties and methods, even without informing the users..**
It's much easier to develop, if you know that certain methods can be renamed, their parameters can be changed, and even removed, because no external code depends on them.
For users, when a new version comes out, it may be a total overhaul, but still simple to upgrade if the external interface is the same.
Hiding complexity
: People adore to use things that are simple. At least from outside. What's inside is a different thing.
Programmers are not an exception.
**It's always convenient when implementation details are hidden, and a simple, well-documented external interface is available.**
To hide internal interface we use either protected or public properties:
- Protected fields start with `_`. That's a well-known convention, not enforced at the language level. Programmers should only access a field starting with `_` from its class and classes inheriting from it.
- Private fields start with `#`. Javascript makes sure we only can access those from inside the class.
Right now, private fields are not well-supported among browsers, but can be polyfilled.

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,82 @@
# Extending build-in classes
Built-in classes like Array, Map and others are extendable also.
For instance, here `PowerArray` inherits from the native `Array`:
```js run
// add one more method to it (can do more)
class PowerArray extends Array {
isEmpty() {
return this.length === 0;
}
}
let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false
let filteredArr = arr.filter(item => item >= 10);
alert(filteredArr); // 10, 50
alert(filteredArr.isEmpty()); // false
```
Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type. They rely on the `constructor` property to do so.
In the example above,
```js
arr.constructor === PowerArray
```
So when `arr.filter()` is called, it internally creates the new array of results exactly as `new PowerArray`.
That's actually very cool, because we can keep using `PowerArray` methods further o the result.
Even more, we can customize that behavior.
There's a special static getter `Symbol.species`, if exists, it returns the constructor to use in such cases.
If we'd like built-in methods like `map`, `filter` will return regular arrays, we can return `Array` in `Symbol.species`, like here:
```js run
class PowerArray extends Array {
isEmpty() {
return this.length === 0;
}
*!*
// built-in methods will use this as the constructor
static get [Symbol.species]() {
return Array;
}
*/!*
}
let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false
// filter creates new array using arr.constructor[Symbol.species] as constructor
let filteredArr = arr.filter(item => item >= 10);
*!*
// filteredArr is not PowerArray, but Array
*/!*
alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
```
As you can see, now `.filter` returns `Array`. So the extended functionality is not passed any further.
## No static inheritance in built-ins
Built-in objects have their own static methods, for instance `Object.keys`, `Array.isArray` etc.
And we've already been talking about native classes extending each other: `Array.[[Prototype]] = Object`.
But statics are an exception. Built-in classes don't inherit static properties from each other.
In other words, the prototype of build-in constructor `Array` does not point to `Object`. This way `Array` and `Date` do not have `Array.keys` or `Date.keys`. And that feels natural.
Here's the picture structure for `Date` and `Object`:
![](object-date-inheritance.png)
Note, there's no link between `Date` and `Object`. Both `Object` and `Date` exist independently. `Date.prototype` inherits from `Object.prototype`, but that's all.

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View file

@ -0,0 +1,7 @@
Yeah, looks strange indeed.
But `instanceof` does not care about the function, but rather about its `prototype`, that it matches against the prototype chain.
And here `a.__proto__ == B.prototype`, so `instanceof` returns `true`.
So, by the logic of `instanceof`, the `prototype` actually defines the type, not the constructor function.

View file

@ -0,0 +1,20 @@
importance: 5
---
# Strange instanceof
Why `instanceof` below returns `true`? We can easily see that `a` is not created by `B()`.
```js run
function A() {}
function B() {}
A.prototype = B.prototype = {};
let a = new A();
*!*
alert( a instanceof B ); // true
*/!*
```

View file

@ -0,0 +1,211 @@
# Class checking: "instanceof"
The `instanceof` operator allows to check whether an object belongs to a certain class. It also takes inheritance into account.
Such a check may be necessary in many cases, here we'll use it for building a *polymorphic* function, the one that treats arguments differently depending on their type.
## The instanceof operator [#ref-instanceof]
The syntax is:
```js
obj instanceof Class
```
It returns `true` if `obj` belongs to the `Class` (or a class inheriting from it).
For instance:
```js run
class Rabbit {}
let rabbit = new Rabbit();
// is it an object of Rabbit class?
*!*
alert( rabbit instanceof Rabbit ); // true
*/!*
```
It also works with constructor functions:
```js run
*!*
// instead of class
function Rabbit() {}
*/!*
alert( new Rabbit() instanceof Rabbit ); // true
```
...And with built-in classes like `Array`:
```js run
let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true
```
Please note that `arr` also belongs to the `Object` class. That's because `Array` prototypally inherits from `Object`.
The `instanceof` operator examines the prototype chain for the check, and is also fine-tunable using the static method `Symbol.hasInstance`.
The algorithm of `obj instanceof Class` works roughly as follows:
1. If there's a static method `Symbol.hasInstance`, then use it. Like this:
```js run
// assume anything that canEat is an animal
class Animal {
static [Symbol.hasInstance](obj) {
if (obj.canEat) return true;
}
}
let obj = { canEat: true };
alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called
```
2. Most classes do not have `Symbol.hasInstance`. In that case, check if `Class.prototype` equals to one of prototypes in the `obj` prototype chain.
In other words, compare:
```js
obj.__proto__ === Class.prototype
obj.__proto__.__proto__ === Class.prototype
obj.__proto__.__proto__.__proto__ === Class.prototype
...
```
In the example above `Rabbit.prototype === rabbit.__proto__`, so that gives the answer immediately.
In the case of an inheritance, `rabbit` is an instance of the parent class as well:
```js run
class Animal {}
class Rabbit extends Animal {}
let rabbit = new Rabbit();
*!*
alert(rabbit instanceof Animal); // true
*/!*
// rabbit.__proto__ === Rabbit.prototype
// rabbit.__proto__.__proto__ === Animal.prototype (match!)
```
Here's the illustration of what `rabbit instanceof Animal` compares with `Animal.prototype`:
![](instanceof.png)
By the way, there's also a method [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf), that returns `true` if `objA` is somewhere in the chain of prototypes for `objB`. So the test of `obj instanceof Class` can be rephrased as `Class.prototype.isPrototypeOf(obj)`.
That's funny, but the `Class` constructor itself does not participate in the check! Only the chain of prototypes and `Class.prototype` matters.
That can lead to interesting consequences when `prototype` is changed.
Like here:
```js run
function Rabbit() {}
let rabbit = new Rabbit();
// changed the prototype
Rabbit.prototype = {};
// ...not a rabbit any more!
*!*
alert( rabbit instanceof Rabbit ); // false
*/!*
```
That's one of the reasons to avoid changing `prototype`. Just to keep safe.
## Bonus: Object toString for the type
We already know that plain objects are converted to string as `[object Object]`:
```js run
let obj = {};
alert(obj); // [object Object]
alert(obj.toString()); // the same
```
That's their implementation of `toString`. But there's a hidden feature that makes `toString` actually much more powerful than that. We can use it as an extended `typeof` and an alternative for `instanceof`.
Sounds strange? Indeed. Let's demystify.
By [specification](https://tc39.github.io/ecma262/#sec-object.prototype.tostring), the built-in `toString` can be extracted from the object and executed in the context of any other value. And its result depends on that value.
- For a number, it will be `[object Number]`
- For a boolean, it will be `[object Boolean]`
- For `null`: `[object Null]`
- For `undefined`: `[object Undefined]`
- For arrays: `[object Array]`
- ...etc (customizable).
Let's demonstrate:
```js run
// copy toString method into a variable for convenience
let objectToString = Object.prototype.toString;
// what type is this?
let arr = [];
alert( objectToString.call(arr) ); // [object Array]
```
Here we used [call](mdn:js/function/call) as described in the chapter [](info:call-apply-decorators) to execute the function `objectToString` in the context `this=arr`.
Internally, the `toString` algorithm examines `this` and returns the corresponding result. More examples:
```js run
let s = Object.prototype.toString;
alert( s.call(123) ); // [object Number]
alert( s.call(null) ); // [object Null]
alert( s.call(alert) ); // [object Function]
```
### Symbol.toStringTag
The behavior of Object `toString` can be customized using a special object property `Symbol.toStringTag`.
For instance:
```js run
let user = {
[Symbol.toStringTag]: "User"
};
alert( {}.toString.call(user) ); // [object User]
```
For most environment-specific objects, there is such a property. Here are few browser specific examples:
```js run
// toStringTag for the envinronment-specific object and class:
alert( window[Symbol.toStringTag]); // window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest
alert( {}.toString.call(window) ); // [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]
```
As you can see, the result is exactly `Symbol.toStringTag` (if exists), wrapped into `[object ...]`.
At the end we have "typeof on steroids" that not only works for primitive data types, but also for built-in objects and even can be customized.
It can be used instead of `instanceof` for built-in objects when we want to get the type as a string rather than just to check.
## Summary
Let's recap the type-checking methods that we know:
| | works for | returns |
|---------------|-------------|---------------|
| `typeof` | primitives | string |
| `{}.toString` | primitives, built-in objects, objects with `Symbol.toStringTag` | string |
| `instanceof` | objects | true/false |
As we can see, `{}.toString` is technically a "more advanced" `typeof`.
And `instanceof` operator really shines when we are working with a class hierarchy and want to check for the class taking into account inheritance.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View file

@ -0,0 +1,205 @@
# Mixins
In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]` for an object. And a class may extend only one other class.
But sometimes that feels limiting. For instance, I have a class `StreetSweeper` and a class `Bicycle`, and want to make a `StreetSweepingBicycle`.
Or, talking about programming, we have a class `Renderer` that implements templating and a class `EventEmitter` that implements event handling, and want to merge these functionalities together with a class `Page`, to make a page that can use templates and emit events.
There's a concept that can help here, called "mixins".
As defined in Wikipedia, a [mixin](https://en.wikipedia.org/wiki/Mixin) is a class that contains methods for use by other classes without having to be the parent class of those other classes.
In other words, a *mixin* provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes.
## A mixin example
The simplest way to make a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class.
For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`:
```js run
*!*
// mixin
*/!*
let sayHiMixin = {
sayHi() {
alert(`Hello ${this.name}`);
},
sayBye() {
alert(`Bye ${this.name}`);
}
};
*!*
// usage:
*/!*
class User {
constructor(name) {
this.name = name;
}
}
// copy the methods
Object.assign(User.prototype, sayHiMixin);
// now User can say hi
new User("Dude").sayHi(); // Hello Dude!
```
There's no inheritance, but a simple method copying. So `User` may extend some other class and also include the mixin to "mix-in" the additional methods, like this:
```js
class User extends Person {
// ...
}
Object.assign(User.prototype, sayHiMixin);
```
Mixins can make use of inheritance inside themselves.
For instance, here `sayHiMixin` inherits from `sayMixin`:
```js run
let sayMixin = {
say(phrase) {
alert(phrase);
}
};
let sayHiMixin = {
__proto__: sayMixin, // (or we could use Object.create to set the prototype here)
sayHi() {
*!*
// call parent method
*/!*
super.say(`Hello ${this.name}`);
},
sayBye() {
super.say(`Bye ${this.name}`);
}
};
class User {
constructor(name) {
this.name = name;
}
}
// copy the methods
Object.assign(User.prototype, sayHiMixin);
// now User can say hi
new User("Dude").sayHi(); // Hello Dude!
```
Please note that the call to the parent method `super.say()` from `sayHiMixin` looks for the method in the prototype of that mixin, not the class.
![](mixin-inheritance.png)
That's because methods from `sayHiMixin` have `[[HomeObject]]` set to it. So `super` actually means `sayHiMixin.__proto__`, not `User.__proto__`.
## EventMixin
Now let's make a mixin for real life.
The important feature of many objects is working with events.
That is: an object should have a method to "generate an event" when something important happens to it, and other objects should be able to "listen" to such events.
An event must have a name and, optionally, bundle some additional data.
For instance, an object `user` can generate an event `"login"` when the visitor logs in. And another object `calendar` may want to receive such events to load the calendar for the logged-in person.
Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may want to get that information and react on that event.
Events is a way to "share information" with anyone who wants it. They can be useful in any class, so let's make a mixin for them:
```js run
let eventMixin = {
/**
* Subscribe to event, usage:
* menu.on('select', function(item) { ... }
*/
on(eventName, handler) {
if (!this._eventHandlers) this._eventHandlers = {};
if (!this._eventHandlers[eventName]) {
this._eventHandlers[eventName] = [];
}
this._eventHandlers[eventName].push(handler);
},
/**
* Cancel the subscription, usage:
* menu.off('select', handler)
*/
off(eventName, handler) {
let handlers = this._eventHandlers && this._eventHandlers[eventName];
if (!handlers) return;
for (let i = 0; i < handlers.length; i++) {
if (handlers[i] === handler) {
handlers.splice(i--, 1);
}
}
},
/**
* Generate the event and attach the data to it
* this.trigger('select', data1, data2);
*/
trigger(eventName, ...args) {
if (!this._eventHandlers || !this._eventHandlers[eventName]) {
return; // no handlers for that event name
}
// call the handlers
this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
}
};
```
There are 3 methods here:
1. `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name happens. The handlers are stored in the `_eventHandlers` property.
2. `.off(eventName, handler)` -- removes the function from the handlers list.
3. `.trigger(eventName, ...args)` -- generates the event: all assigned handlers are called and `args` are passed as arguments to them.
Usage:
```js run
// Make a class
class Menu {
choose(value) {
this.trigger("select", value);
}
}
// Add the mixin
Object.assign(Menu.prototype, eventMixin);
let menu = new Menu();
// call the handler on selection:
*!*
menu.on("select", value => alert(`Value selected: ${value}`));
*/!*
// triggers the event => shows Value selected: 123
menu.choose("123"); // value selected
```
Now if we have the code interested to react on user selection, we can bind it with `menu.on(...)`.
And the `eventMixin` can add such behavior to as many classes as we'd like, without interfering with the inheritance chain.
## Summary
*Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes.
Some other languages like e.g. python allow to create mixins using multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying them into the prototype.
We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above.
Mixins may become a point of conflict if they occasionally overwrite native class methods. So generally one should think well about the naming for a mixin, to minimize such possibility.

View file

@ -0,0 +1,43 @@
<script>
let eventMixin = {
/**
* Subscribe to event, usage:
* menu.on('select', function(item) { ... }
*/
on(eventName, handler) {
if (!this._eventHandlers) this._eventHandlers = {};
if (!this._eventHandlers[eventName]) {
this._eventHandlers[eventName] = [];
}
this._eventHandlers[eventName].push(handler);
},
/**
* Cancel the subscription, usage:
* menu.off('select', handler)
*/
off(eventName, handler) {
let handlers = this._eventHandlers && this._eventHandlers[eventName];
if (!handlers) return;
for(let i = 0; i < handlers.length; i++) {
if (handlers[i] == handler) {
handlers.splice(i--, 1);
}
}
},
/**
* Generate the event and attach the data to it
* this.trigger('select', data1, data2);
*/
trigger(eventName, ...args) {
if (!this._eventHandlers || !this._eventHandlers[eventName]) {
return; // no handlers for that event name
}
// call the handlers
this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
}
};
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

1
1-js/09-classes/index.md Normal file
View file

@ -0,0 +1 @@
# Classes