refactor classes, add private, minor fixes
This commit is contained in:
parent
a0c07342ad
commit
1373f6158c
270 changed files with 1513 additions and 890 deletions
272
1-js/09-classes/02-class/article.md
Normal file
272
1-js/09-classes/02-class/article.md
Normal file
|
@ -0,0 +1,272 @@
|
|||
|
||||
# Classes
|
||||
|
||||
The "class" construct allows to define prototype-based classes with a clean, nice-looking syntax. It also introduces new great features, useful for object-oriented programming.
|
||||
|
||||
## The "class" syntax
|
||||
|
||||
The `class` syntax is versatile, we'll start with a simple example first.
|
||||
|
||||
Here's a prototype-based class `User`:
|
||||
|
||||
```js run
|
||||
function User(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
User.prototype.sayHi = function() {
|
||||
alert(this.name);
|
||||
}
|
||||
|
||||
let user = new User("John");
|
||||
user.sayHi();
|
||||
```
|
||||
|
||||
...And that's the same using `class` syntax:
|
||||
|
||||
```js run
|
||||
class User {
|
||||
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
sayHi() {
|
||||
alert(this.name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let user = new User("John");
|
||||
user.sayHi();
|
||||
```
|
||||
|
||||
It's easy to see that the two examples are alike. Just please note that methods in a class do not have a comma between them. Novice developers sometimes forget it and put a comma between class methods, and things don't work. That's not a literal object, but a class syntax.
|
||||
|
||||
## What is a class?
|
||||
|
||||
So, what exactly is a `class`? We may think that it defines a new language-level entity, but that would be wrong.
|
||||
|
||||
In Javascript, a class is a kind of a function.
|
||||
|
||||
The definition `class User {...}` create such function and puts the methods into `User.prototype`. So the structure is similar.
|
||||
|
||||
Here's the code to dig into the class and see that:
|
||||
|
||||
```js run
|
||||
class User {
|
||||
constructor(name) { this.name = name; }
|
||||
sayHi() { alert(this.name); }
|
||||
}
|
||||
|
||||
*!*
|
||||
// proof: User is a function
|
||||
alert(typeof User); // function
|
||||
*/!*
|
||||
|
||||
*!*
|
||||
// proof: User is the "constructor" function
|
||||
*/!*
|
||||
alert(User === User.prototype.constructor); // true
|
||||
|
||||
*!*
|
||||
// proof: there are two methods in its "prototype"
|
||||
*/!*
|
||||
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
|
||||
```
|
||||
|
||||
Here's the illustration of what `class User` creates:
|
||||
|
||||

|
||||
|
||||
So `class` is a special syntax to define a constructor together with its prototype methods. There are also quite a few additional features here and there, we'll study them later on.
|
||||
|
||||
## Class Expression
|
||||
|
||||
Just like functions, classes can be defined inside another expression, passed around, returned etc.
|
||||
|
||||
Here's a class-returning function ("class factory"):
|
||||
|
||||
```js run
|
||||
function makeClass(phrase) {
|
||||
*!*
|
||||
// declare a class and return it
|
||||
return class {
|
||||
sayHi() {
|
||||
alert(phrase);
|
||||
};
|
||||
};
|
||||
*/!*
|
||||
}
|
||||
|
||||
let User = makeClass("Hello");
|
||||
|
||||
new User().sayHi(); // Hello
|
||||
```
|
||||
|
||||
That's quite normal if we recall that `class` is just a special form of a function-with-prototype definition.
|
||||
|
||||
And, like Named Function Expressions, such classes also may have a name, that is visible inside that class only:
|
||||
|
||||
```js run
|
||||
// "Named Class Expression" (alas, no such term, but that's what's going on)
|
||||
let User = class *!*MyClass*/!* {
|
||||
sayHi() {
|
||||
alert(MyClass); // MyClass is visible only inside the class
|
||||
}
|
||||
};
|
||||
|
||||
new User().sayHi(); // works, shows MyClass definition
|
||||
|
||||
alert(MyClass); // error, MyClass not visible outside of the class
|
||||
```
|
||||
|
||||
## Differences of classes vs functions
|
||||
|
||||
Classes have some differences compared to regular functions:
|
||||
|
||||
Constructors require `new`
|
||||
: Unlike a regular function, a class `constructor` can't be called without `new`:
|
||||
|
||||
```js run
|
||||
class User {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
alert(typeof User); // function
|
||||
User(); // Error: Class constructor User cannot be invoked without 'new'
|
||||
```
|
||||
|
||||
Different string output
|
||||
: If we output it like `alert(User)`, some engines show `"class User..."`, while others show `"function User..."`.
|
||||
|
||||
Please don't be confused: the string representation may vary, but that's still a function, there is no separate "class" entity in JavaScript language.
|
||||
|
||||
Class methods are non-enumerable
|
||||
: A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. That's good, because if we `for..in` over an object, we usually don't want its class methods.
|
||||
|
||||
Classes have a default `constructor() {}`
|
||||
: If there's no `constructor` in the `class` construct, then an empty function is generated, same as if we had written `constructor() {}`.
|
||||
|
||||
Classes always `use strict`
|
||||
: All code inside the class construct is automatically in strict mode.
|
||||
|
||||
|
||||
## Getters/setters, other shorthands
|
||||
|
||||
Classes also include getters/setters, generators, computed properties etc.
|
||||
|
||||
Here's an example for `user.name` implemented using `get/set`:
|
||||
|
||||
```js run
|
||||
class User {
|
||||
|
||||
constructor(name) {
|
||||
// invokes the setter
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
*!*
|
||||
get name() {
|
||||
*/!*
|
||||
return this._name;
|
||||
}
|
||||
|
||||
*!*
|
||||
set name(value) {
|
||||
*/!*
|
||||
if (value.length < 4) {
|
||||
alert("Name is too short.");
|
||||
return;
|
||||
}
|
||||
this._name = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let user = new User("John");
|
||||
alert(user.name); // John
|
||||
|
||||
user = new User(""); // Name too short.
|
||||
```
|
||||
|
||||
Internally, getters and setters are created on `User.prototype`, like this:
|
||||
|
||||
```js
|
||||
Object.defineProperties(User.prototype, {
|
||||
name: {
|
||||
get() {
|
||||
return this._name
|
||||
},
|
||||
set(name) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Here's an example with computed properties:
|
||||
|
||||
```js run
|
||||
function f() { return "sayHi"; }
|
||||
|
||||
class User {
|
||||
[f()]() {
|
||||
alert("Hello");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new User().sayHi();
|
||||
```
|
||||
|
||||
For a generator method, similarly, prepend it with `*`.
|
||||
|
||||
## Class properties
|
||||
|
||||
```warn header="Old browsers may need a polyfill"
|
||||
Class-level properties is a recent addition to the language.
|
||||
```
|
||||
|
||||
In the example before, `User` only had methods. Let's add a property:
|
||||
|
||||
```js run
|
||||
class User {
|
||||
name = "Anonymous";
|
||||
|
||||
sayHi() {
|
||||
alert(`Hello, ${this.name}!`);
|
||||
}
|
||||
}
|
||||
|
||||
new User().sayHi();
|
||||
```
|
||||
|
||||
The property is not placed into `User.prototype`. Instead, it is created by `new`, separately for every object. So, the property will never be shared between different objects of the same class.
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
The basic class syntax looks like this:
|
||||
|
||||
```js
|
||||
class MyClass {
|
||||
prop = value;
|
||||
|
||||
constructor(...) {
|
||||
// ...
|
||||
}
|
||||
|
||||
method(...) {}
|
||||
|
||||
get something(...) {}
|
||||
set something(...) {}
|
||||
|
||||
[Symbol.iterator]() {}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
`MyClass` is technically a function, while methods are written to `MyClass.prototype`.
|
||||
|
||||
In the next chapters we'll learn more about classes, including inheritance and other features.
|
Loading…
Add table
Add a link
Reference in a new issue