classes: remove patterns
|
@ -1,46 +0,0 @@
|
|||
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`.
|
|
@ -1,29 +0,0 @@
|
|||
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!");
|
||||
};
|
||||
```
|
|
@ -1 +0,0 @@
|
|||
Please note that properties that were internal in functional style (`template`, `timer`) and the internal method `render` are marked private with the underscore `_`.
|
|
@ -1,32 +0,0 @@
|
|||
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);
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
<!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>
|
|
@ -1,18 +0,0 @@
|
|||
<!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>
|
|
@ -1,9 +0,0 @@
|
|||
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.
|
|
@ -1,240 +0,0 @@
|
|||
|
||||
# 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 not 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.
|
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 36 KiB |
|
@ -32,3 +32,7 @@ class Clock {
|
|||
this.timer = setInterval(() => this.render(), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let clock = new Clock({template: 'h:m:s'});
|
||||
clock.start();
|
|
@ -32,3 +32,6 @@ function Clock({ template }) {
|
|||
};
|
||||
|
||||
}
|
||||
|
||||
let clock = new Clock({template: 'h:m:s'});
|
||||
clock.start();
|
|
@ -4,6 +4,6 @@ importance: 5
|
|||
|
||||
# Rewrite to class
|
||||
|
||||
Rewrite the `Clock` class from prototypes to the modern "class" syntax.
|
||||
The `Clock` class is written in functional style. Rewrite it the "class" syntax.
|
||||
|
||||
P.S. The clock ticks in the console, open it to see.
|
368
1-js/09-classes/01-class/article.md
Normal file
|
@ -0,0 +1,368 @@
|
|||
|
||||
# Classes
|
||||
|
||||
```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).
|
||||
```
|
||||
|
||||
In practice, we often need to create many objects of the same kind, like users, or goods or whatever.
|
||||
|
||||
As we already know from the chapter <info:constructor-new>, `new function` can help with that.
|
||||
|
||||
But in the modern JavaScript, there's a more advanced "class" construct, that introduces great new features which are useful for object-oriented programming.
|
||||
|
||||
## The "class" syntax
|
||||
|
||||
The basic syntax is:
|
||||
```js
|
||||
class MyClass {
|
||||
// class methods
|
||||
constructor() { ... }
|
||||
method1() { ... }
|
||||
method2() { ... }
|
||||
method3() { ... }
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Then `new MyClass()` creates a new object with all the listed methods.
|
||||
|
||||
The `constructor()` method is called automatically by `new`, so we can initialize the object there.
|
||||
|
||||
For example:
|
||||
|
||||
```js run
|
||||
class User {
|
||||
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
sayHi() {
|
||||
alert(this.name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Usage:
|
||||
let user = new User("John");
|
||||
user.sayHi();
|
||||
```
|
||||
|
||||
When `new User("John")` is called:
|
||||
1. A new object is created.
|
||||
2. The `constructor` runs with the given argument and assigns `this.name` to it.
|
||||
|
||||
...Then we can call methods, such as `user.sayHi`.
|
||||
|
||||
|
||||
```warn header="No comma between class methods"
|
||||
A common pitfall for novice developers is to put a comma between class methods, which would result in a syntax error.
|
||||
|
||||
The notation here is not to be confused with object literals. Within the class, no commas are required.
|
||||
```
|
||||
|
||||
## What is a class?
|
||||
|
||||
So, what exactly is a `class`? That's not an entirely new language-level entity, as one might think.
|
||||
|
||||
Let's unveil any magic and see what a class really is. That'll help in understanding many complex aspects.
|
||||
|
||||
In Javascript, a class is a kind of a function.
|
||||
|
||||
Here, take a look:
|
||||
|
||||
```js run
|
||||
class User {
|
||||
constructor(name) { this.name = name; }
|
||||
sayHi() { alert(this.name); }
|
||||
}
|
||||
|
||||
// proof: User is a function
|
||||
*!*
|
||||
alert(typeof User); // function
|
||||
*/!*
|
||||
```
|
||||
|
||||
What `class User {...}` construct really does is:
|
||||
1. Creates a function named `User`, that becomes the result of the class declaration.
|
||||
- The function code is taken from the `constructor` method (assumed empty is we don't write such method).
|
||||
3. Stores all methods, such as `sayHi`, in `User.prototype`.
|
||||
|
||||
Afterwards, for new objects, when we call a method, it's taken from the prototype, just as described in the chapter <info:function-prototype>. So `new User` object has access to class methods.
|
||||
|
||||
We can illustrate the result of `class User` as:
|
||||
|
||||

|
||||
|
||||
Here's the code to introspect it:
|
||||
|
||||
|
||||
```js run
|
||||
class User {
|
||||
constructor(name) { this.name = name; }
|
||||
sayHi() { alert(this.name); }
|
||||
}
|
||||
|
||||
// class is a function
|
||||
alert(typeof User); // function
|
||||
|
||||
// ...or, more precisely, the constructor method
|
||||
alert(User === User.prototype.constructor); // true
|
||||
|
||||
// The methods are in User.prototype, e.g:
|
||||
alert(User.prototype.sayHi); // alert(this.name);
|
||||
|
||||
// there are exactly two methods in the prototype
|
||||
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
|
||||
```
|
||||
|
||||
## Not just a syntax sugar
|
||||
|
||||
Sometimes people say that `class` is a "syntax sugar" in JavaScript, because we could actually declare the same without `class` keyword at all:
|
||||
|
||||
```js run
|
||||
// rewriting class User in pure functions
|
||||
|
||||
// 1. Create constructor function
|
||||
function User(name) {
|
||||
this.name = name;
|
||||
}
|
||||
// any function prototype has constructor property by default,
|
||||
// so we don't need to create it
|
||||
|
||||
// 2. Add the method to prototype
|
||||
User.prototype.sayHi = function() {
|
||||
alert(this.name);
|
||||
};
|
||||
|
||||
// Usage:
|
||||
let user = new User("John");
|
||||
user.sayHi();
|
||||
```
|
||||
|
||||
The result of this definition is about the same. So, there are indeed reasons why `class` can be considered a syntax sugar to define a constructor together with its prototype methods.
|
||||
|
||||
Although, there are important differences.
|
||||
|
||||
1. First, a function created by `class` is labelled by a special internal property `[[FunctionKind]]:"classConstructor"`. So it's not entirely the same as creating it manually.
|
||||
|
||||
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'
|
||||
```
|
||||
|
||||
Also, a string representation of a class constructor in most JavaScript engines starts with the "class..."
|
||||
|
||||
```js run
|
||||
class User {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
alert(User); // class User { ... }
|
||||
```
|
||||
|
||||
2. 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.
|
||||
|
||||
3. Classes always `use strict`
|
||||
All code inside the class construct is automatically in strict mode.
|
||||
|
||||
|
||||
Also, in addition to its basic operation, the `class` syntax brings many other features with it which we'll explore later.
|
||||
|
||||
## Class Expression
|
||||
|
||||
Just like functions, classes can be defined inside another expression, passed around, returned, assigned etc.
|
||||
|
||||
Here's an example of a class expression:
|
||||
|
||||
```js
|
||||
let User = class {
|
||||
sayHi() {
|
||||
alert("Hello");
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Similar to Named Function Expressions, class expressions may or may not have a name.
|
||||
|
||||
If a class expression has a name, it's visible inside the 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
|
||||
```
|
||||
|
||||
|
||||
We can even make classes dynamically "on-demand", like this:
|
||||
|
||||
```js run
|
||||
function makeClass(phrase) {
|
||||
// declare a class and return it
|
||||
return class {
|
||||
sayHi() {
|
||||
alert(phrase);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Create a new class
|
||||
let User = makeClass("Hello");
|
||||
|
||||
new User().sayHi(); // Hello
|
||||
```
|
||||
|
||||
|
||||
## 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 are a recent addition to the language.
|
||||
```
|
||||
|
||||
In the example above, `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
|
||||
|
||||
JavaScript provides many ways to create a class.
|
||||
|
||||
First, as per the general object-oriented terminology, a class is something that provides "object templates", allows to create same-structured objects.
|
||||
|
||||
When we say "a class", that doesn't necessary means the `class` keyword.
|
||||
|
||||
This is a class:
|
||||
|
||||
```js
|
||||
function User(name) {
|
||||
this.sayHi = function() {
|
||||
alert(name);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
...But in most cases `class` keyword is used, as it provides great syntax and many additional features.
|
||||
|
||||
The basic class syntax looks like this:
|
||||
|
||||
```js
|
||||
class MyClass {
|
||||
prop = value; // field
|
||||
|
||||
constructor(...) { // constructor
|
||||
// ...
|
||||
}
|
||||
|
||||
method(...) {} // method
|
||||
|
||||
get something(...) {} // getter method
|
||||
set something(...) {} // setter method
|
||||
|
||||
[Symbol.iterator]() {} // method with computed name/symbol name
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
`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.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
BIN
1-js/09-classes/02-class-inheritance/animal-rabbit-extends.png
Normal file
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 76 KiB |
|
@ -1,7 +1,53 @@
|
|||
|
||||
# Class inheritance
|
||||
|
||||
Classes can extend one another. There's a nice syntax, technically based on the prototypal inheritance.
|
||||
Let's say we have two classes.
|
||||
|
||||
`Animal`:
|
||||
|
||||
```js
|
||||
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.`);
|
||||
}
|
||||
}
|
||||
|
||||
let animal = new Animal("My animal");
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
...And `Rabbit`:
|
||||
|
||||
```js
|
||||
class Rabbit extends Animal {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
hide() {
|
||||
alert(`${this.name} hides!`);
|
||||
}
|
||||
}
|
||||
|
||||
let rabbit = new Rabbit("My rabbit");
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
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.
|
||||
|
||||
To inherit from another class, we should specify `"extends"` and the parent class before the brackets `{..}`.
|
||||
|
||||
|
@ -9,22 +55,18 @@ 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.`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
*!*
|
||||
|
@ -42,11 +84,15 @@ 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.
|
||||
Now the `Rabbit` code became a bit shorter, as it uses `Animal` constructor by default, and it also can `run`, as animals do.
|
||||
|
||||
Internally, `extends` keyword adds `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`:
|
||||
|
||||

|
||||
|
||||
So now `rabbit` has access both to its own methods and to methods of `Animal`.
|
||||
So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`.
|
||||
|
||||
As we can recall from the chapter <info:native-prototypes>, JavaScript uses the same prototypal inheritance for build-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`, so dates have generic object methods.
|
||||
|
||||
````smart header="Any expression is allowed after `extends`"
|
||||
Class syntax allows to specify not just a class, but any expression after `extends`.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
@ -1,18 +0,0 @@
|
|||
<!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>
|
|
@ -1,34 +0,0 @@
|
|||
|
||||
|
||||
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);
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
<!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>
|
|
@ -1,272 +0,0 @@
|
|||
|
||||
# Classes
|
||||
|
||||
The "class" construct allows one to define prototype-based classes with a clean, nice-looking syntax. It also introduces great new features which are 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 here'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 these two examples are alike. Be sure to note that methods in a class do not have a comma between them. A common pitfall for novice developers is to put a comma between class methods, which would result in a syntax error. The notation here is not to be confused with object literals. Within the class syntactical sugar, no commas are required.
|
||||
|
||||
## 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 function.
|
||||
|
||||
The definition `class User {...}` creates a function under the same name and puts the methods into `User.prototype`. So the structure is similar.
|
||||
|
||||
This is demonstrated in the following code, which you can run yourself:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Abstractly, we can illustrate this process of `class User` creating a function as:
|
||||
|
||||

|
||||
|
||||
`Class` is a special syntax to define a constructor together with its prototype methods. In addition to its basic operation, the `Class` syntax brings many other features with it which we'll explore later.
|
||||
|
||||
## Class Expression
|
||||
|
||||
Just like functions, classes can be defined inside another expression, passed around, returned etc.
|
||||
|
||||
Here's a class-returning function - otherwise known as a "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 between classes and 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, just 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 are a recent addition to the language.
|
||||
```
|
||||
|
||||
In the example above, `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.
|
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |