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);
|
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 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.
|
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
|
# 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 `{..}`.
|
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
|
```js run
|
||||||
class Animal {
|
class Animal {
|
||||||
|
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
this.speed = 0;
|
this.speed = 0;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
run(speed) {
|
run(speed) {
|
||||||
this.speed += speed;
|
this.speed += speed;
|
||||||
alert(`${this.name} runs with speed ${this.speed}.`);
|
alert(`${this.name} runs with speed ${this.speed}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.speed = 0;
|
this.speed = 0;
|
||||||
alert(`${this.name} stopped.`);
|
alert(`${this.name} stopped.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
|
@ -42,11 +84,15 @@ rabbit.run(5); // White Rabbit runs with speed 5.
|
||||||
rabbit.hide(); // White Rabbit hides!
|
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`"
|
````smart header="Any expression is allowed after `extends`"
|
||||||
Class syntax allows to specify not just a class, but any expression 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 |