classes: remove patterns

This commit is contained in:
Ilya Kantor 2019-04-21 13:40:15 +03:00
parent be9f48c2f2
commit b8eb04dfb6
76 changed files with 429 additions and 743 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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");
```
![](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.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View file

@ -32,3 +32,7 @@ class Clock {
this.timer = setInterval(() => this.render(), 1000);
}
}
let clock = new Clock({template: 'h:m:s'});
clock.start();

View file

@ -32,3 +32,6 @@ function Clock({ template }) {
};
}
let clock = new Clock({template: 'h:m:s'});
clock.start();

View file

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

View 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:
![](class-user.png)
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.

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View file

@ -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");
```
![](rabbit-animal-independent-animal.png)
...And `Rabbit`:
```js
class Rabbit extends Animal {
constructor(name) {
this.name = name;
}
hide() {
alert(`${this.name} hides!`);
}
}
let rabbit = new Rabbit("My rabbit");
```
![](rabbit-animal-independent-rabbit.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.
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`:
![](animal-rabbit-extends.png)
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`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before After
Before After

View file

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

View file

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

View file

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

View file

@ -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-user.png)
`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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before After
Before After