This commit is contained in:
Ilya Kantor 2017-06-29 16:27:58 +03:00
parent 2a0516d174
commit 40ca6daa87
6 changed files with 709 additions and 55 deletions

View file

@ -43,14 +43,16 @@ let user = new User("John");
user.sayHi();
```
It's easy to see that the two examples are alike. So, what exactly does `class` do? We may think that it defines a new language-level entity, but that would be wrong.
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. Notice 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.
So, what exactly does `class` do? We may think that it defines a new language-level entity, but that would be wrong.
The `class User {...}` here actually does two things:
1. Declares a variable `User` that references the function named `"constructor"`.
2. Puts into `User.prototype` methods listed in the definition. Here it includes `sayHi` and the `constructor`.
Here's some code to demonstrate that:
Here's the code to dig into the class and see that:
```js run
class User {
@ -69,15 +71,19 @@ alert(User == User.prototype.constructor); // true
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
```
Here's the illustration of `class User`:
Here's the illustration of what `class User` creates:
![](class-user.png)
So `class` is a special syntax to define the constructor with prototype methods.
...But not only that. There are minor tweaks here and there to ensure the right usage.
For instance, the `constructor` function can't be called without `new`:
So `class` is a special syntax to define a constructor together with its prototype methods.
...But not only that. There are minor tweaks here and there:
Constructors require `new`
: Unlike a regular function, a class `constructor` can't be called without `new`:
```js run
class User {
constructor() {}
@ -87,23 +93,19 @@ alert(typeof User); // function
User(); // Error: Class constructor User cannot be invoked without 'new'
```
```smart header="Outputting a class"
If we output it like `alert(User)`, some engines show `"class User..."`, while others show `"function User..."`.
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.
```
```smart header="Class methods are non-enumerable"
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.
```
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.
```smart header="What if there's no constructor?"
If there's no `constructor` in the `class` construct, then an empty function is generated, same as if we had written `constructor() {}`.
```
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() {}`.
```smart header="Classes always `use strict`"
All code inside the class construct is automatically in strict mode.
```
Classes always `use strict`
: All code inside the class construct is automatically in strict mode.
### Getters/setters
@ -141,13 +143,26 @@ alert(user.name); // John
user = new User(""); // Name too short.
```
Internally, getters and setters are also created on the `User` prototype, like this:
```js
Object.defineProperty(User.prototype, {
name: {
get() {
return this._name
},
set(name) {
// ...
}
}
});
```
### Only methods
Unlike object literals, no `property:value` assignments are allowed inside `class`. There may be only methods (without a comma between them) and getters/setters.
Unlike object literals, no `property:value` assignments are allowed inside `class`. There may be only methods and getters/setters. There is some work going on in the specification to lift that limitation, but it's not yet there.
The idea is that everything inside `class` goes to the prototype. And the prototype should store methods only, which are shared between objects. The data describing a concrete object state should reside in individual objects.
If we really insist on putting a non-function value into the prototype, then `class` can't help here. We can alter `prototype` manually though, like this:
If we really need to put a non-function value into the prototype, then we can alter `prototype` manually, like this:
```js run
class User { }
@ -157,9 +172,9 @@ User.prototype.test = 5;
alert( new User().test ); // 5
```
So, technically that's possible, but we should know why we're doing it.
So, technically that's possible, but we should know why we're doing it. Such properties will be shared among all objects of the class.
An alternative here would be to use a getter:
An "in-class" alternative is to use a getter:
```js run
class User {
@ -171,7 +186,7 @@ class User {
alert( new User().test ); // 5
```
From the external code, the usage is the same. But the getter variant is probably a bit slower.
From the external code, the usage is the same. But the getter variant is a bit slower.
## Class Expression
@ -180,8 +195,9 @@ Just like functions, classes can be defined inside another expression, passed ar
Here's a class-returning function ("class factory"):
```js run
function getClass(phrase) {
function makeClass(phrase) {
*!*
// declare a class and return it
return class {
sayHi() {
alert(phrase);
@ -190,30 +206,31 @@ function getClass(phrase) {
*/!*
}
let User = getClass("Hello");
let User = makeClass("Hello");
new User().sayHi(); // Hello
```
That's quite normal if we recall that `class` is just a special form of function-with-prototype definition.
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);
alert(MyClass); // MyClass is visible only inside the class
}
};
new User().sayHi(); // works, shows MyClass definition
alert(MyClass); // error, MyClass is only visible in methods of the class
alert(MyClass); // error, MyClass not visible outside of the class
```
## Static methods
Static methods are bound to the class function, not to its `"prototype"`.
We can also assign methods to the class function, not to its `"prototype"`. Such methods are called *static*.
An example:
@ -241,7 +258,7 @@ User.staticMethod = function() {
The value of `this` inside `User.staticMethod()` is the class constructor `User` itself (the "object before dot" rule).
Usually, static methods are used when the code is related to the class, but not to a particular object of it.
Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it.
For instance, we have `Article` objects and need a function to compare them. The natural choice would be `Article.compare`, like this:
@ -273,9 +290,15 @@ articles.sort(Article.compare);
alert( articles[0].title ); // Body
```
Here `Article.compare` stands "over" the articles, as a means to compare them.
Here `Article.compare` stands "over" the articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
Another example would be a so-called "factory" method, that creates an object with specific parameters.
Another example would be a so-called "factory" method. Imagine, we need few ways to create an article:
1. Create by given parameters (`title`, `date` etc).
2. Create an empty article with today's date.
3. ...
The first way can be implemented by the constructor. And for the second one we can make a static method of the class.
Like `Article.createTodays()` here:
@ -299,12 +322,36 @@ let article = Article.createTodays();
alert( articles.title ); // Todays digest
```
Now every time we need to create a todays digest, we can call `Article.createTodays()`.
Now every time we need to create a todays digest, we can call `Article.createTodays()`. Once again, that's not a method of an article, but a method of the whole class.
Static methods are often used in database-related classes to search/save/remove entries from the database, like this:
Static methods are also used in database-related classes to search/save/remove entries from the database, like this:
```js
// assuming Article is a special class for managing articles
// static method to remove the article:
Article.remove({id: 12345});
```
## Summary
The basic class syntax looks like this:
```js
class MyClass {
constructor(...) {
// ...
}
method1(...) {}
method2(...) {}
get something(...) {}
set something(...) {}
static staticMethod(..) {}
// ...
}
```
The value of `MyClass` is a function provided as `constructor`. If there's no `constructor`, then an empty function.
In any case, methods listed in the class declaration become members of its `prototype`, with the exception of static methods that are written into the function itself and callable as `MyClass.staticMethod()`. Static methods are used when we need a function bound to a class, but not to any object of that class.
In the next chapter we'll learn more about classes, including inheritance.

View file

@ -1,18 +1,20 @@
# Mixins
In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]`.
In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]` for an object. And a class may extend only one other class.
But sometimes we need such kind of thing. For instance, we have a code that implements events exchange or templating, and we'd like to be able to add these capabilities to any class easily.
But sometimes that feels limiting. For instance, I have a class `StreetSweeper` and a class `Bycicle`, and want to make a `StreetSweepingBycicle`.
What can help here is *mixins*.
Or, talking about programming, we have a class `Renderer` that implements templating and a class `EventEmitter` that implements event handling, and want to merge these functionalities together with a class `Page`, to make a page that can use templates and emit events.
As [defined in Wikipedia](https://en.wikipedia.org/wiki/Mixin), a *mixin* is a class that contains methods for use by other classes without having to be the parent class of those other classes.
There's a concept that can help here, called "mixins".
In other words, a *mixin* is a class that implements a certain behavior. But we do not use it alone, we use it to add the behavior to other classes.
As defined in Wikipedia, a [mixin](https://en.wikipedia.org/wiki/Mixin) is a class that contains methods for use by other classes without having to be the parent class of those other classes.
In other words, a *mixin* provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes.
## A mixin example
The simplest way to make a mixin in JavaScript -- is to make an object with useful methods, that we can just copy into the prototype.
The simplest way to make a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class.
For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`:
@ -45,9 +47,17 @@ Object.assign(User.prototype, sayHiMixin);
new User("Dude").sayHi(); // Hi Dude!
```
There's no inheritance, there's a simple method copying. So `User` may extend some other class and also include the mixin to "mix-in" the additional methods.
There's no inheritance, but a simple method copying. So `User` may extend some other class and also include the mixin to "mix-in" the additional methods, like this:
Mixins also can make use of inheritance.
```js
class User extends Person {
// ...
}
Object.assign(User.prototype, sayHiMixin);
```
Mixins can make use of inheritance inside themselves.
For instance, here `sayHiMixin` inherits from `sayMixin`:
@ -59,8 +69,7 @@ let sayMixin = {
};
let sayHiMixin = {
// can use any way of prototype setting here
__proto__: sayMixin,
__proto__: sayMixin, // (or we could use Object.create to set the prototype here)
sayHi() {
*!*
@ -94,25 +103,22 @@ That's because methods from `sayHiMixin` have `[[HomeObject]]` set to it. So `su
## EventMixin
Now a mixin for the real life.
Now let's make a mixin for real life.
The important feature of many objects is working with events.
That is: an object should have a method to "generate an event" when something important happens to it, and other objects should be able to "subscribe" to receive such notifications.
That is: an object should have a method to "generate an event" when something important happens to it, and other objects should be able to "listen" to such events.
An event must have a name and, if necessary, the attached data.
An event must have a name and, optionally, bundle some additional data.
For instance, an object `user` can generate an event `"login"` when the visitor logs in. And an object `calendar` may want to receive such notifications and load the information about that visitor.
For instance, an object `user` can generate an event `"login"` when the visitor logs in. And another object `calendar` may want to receive such events to load the calendar for the logged-in person.
Or, the object `menu` can generate the event `"select"` when a menu item is selected, and other objects may want to get that information and react on that event.
Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may want to get that information and react on that event.
Events is a way to "share information" with anyone who wants it.
Here is `eventMixin` that implements the corresponding methods:
Events is a way to "share information" with anyone who wants it. They can be useful in any class, so let's make a mixin for them:
```js run
let eventMixin = {
/**
* Subscribe to event, usage:
* menu.on('select', function(item) { ... }