This commit is contained in:
Ilya Kantor 2019-06-21 21:42:57 +03:00
parent 2ca9d97df5
commit 1b858a03f2
3 changed files with 41 additions and 29 deletions

View file

@ -14,7 +14,6 @@ The latest draft is at <https://tc39.es/ecma262/>.
To read about bleeding-edge features, that are not yet widely supported, see proposals at <https://github.com/tc39/proposals>. To read about bleeding-edge features, that are not yet widely supported, see proposals at <https://github.com/tc39/proposals>.
Also, if you're in developing for the browser, then there are other specs covered in the [second part](info:browser-environment) of the tutorial. Also, if you're in developing for the browser, then there are other specs covered in the [second part](info:browser-environment) of the tutorial.
## Manuals ## Manuals

View file

@ -45,7 +45,19 @@ alert(test); // true, the variable lives after if
*/!* */!*
``` ```
If we used `let test` on the 2nd line, then it wouldn't be visible to `alert`. But `var` ignores code blocks, so we've got a global `test`. `var` ignores code blocks, so we've got a global variable `test`.
If we used `let test` instead of `var test`, then the variable would only be visible inside `if`:
```js run
if (true) {
let test = true; // use "let"
}
*!*
alert(test); // Error: test is not defined
*/!*
```
The same thing for loops: `var` cannot be block- or loop-local: The same thing for loops: `var` cannot be block- or loop-local:
@ -76,7 +88,7 @@ alert(phrase); // Error: phrase is not defined (Check the Developer Console)
As we can see, `var` pierces through `if`, `for` or other code blocks. That's because a long time ago in JavaScript blocks had no Lexical Environments. And `var` is a remnant of that. As we can see, `var` pierces through `if`, `for` or other code blocks. That's because a long time ago in JavaScript blocks had no Lexical Environments. And `var` is a remnant of that.
## "var" are processed at the function start ## "var" declarations are processed at the function start
`var` declarations are processed when the function starts (or script starts for globals). `var` declarations are processed when the function starts (or script starts for globals).
@ -178,11 +190,11 @@ In both examples above `alert` runs without an error, because the variable `phra
## Summary ## Summary
There are two main differences of `var`: There are two main differences of `var` compared to `let/const`:
1. Variables have no block scope, they are visible minimum at the function level. 1. `var` variables have no block scope, they are visible minimum at the function level.
2. Variable declarations are processed at function start. 2. `var` declarations are processed at function start (script start for globals).
There's one more minor difference related to the global object, we'll cover that in the next chapter. There's one more minor difference related to the global object, we'll cover that in the next chapter.
These differences are actually a bad thing most of the time. Block-level variables is such a great thing. That's why `let` was introduced in the standard long ago, and is now a major way (along with `const`) to declare a variable. These differences make `var` worse than `let` most of the time. Block-level variables is such a great thing. That's why `let` was introduced in the standard long ago, and is now a major way (along with `const`) to declare a variable.

View file

@ -99,23 +99,25 @@ Please note that the call to the parent method `super.say()` from `sayHiMixin` l
![](mixin-inheritance.png) ![](mixin-inheritance.png)
That's because methods from `sayHiMixin` have `[[HomeObject]]` set to it. So `super` actually means `sayHiMixin.__proto__`, not `User.__proto__`. That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So their `[[HomeObject]]` internal property references `sayHiMixin`, as shown on the picture above.
As `super` looks for parent methods in `[[HomeObject]].__proto__`, that means it searches `sayHiMixin.__proto__` for `super.say`, not `User.__proto__`.
## EventMixin ## EventMixin
Now let's make a mixin for real life. Now let's make a mixin for real life.
The important feature of many objects is working with events. The important feature of many browser objects (not only) is working with events. Events is a way to "broadcast information" to anyone who wants it. So let's make a mixin that allows to easily add event-related functions to any class/object.
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. - The mixin will provide a method `.trigger(name, [data])` to "generate an event" when something important happens to it. The `name` argument is a name of the event, and the optional additional data may follow.
- Also the method `.on(name, handler)` that adds `handler` function as the listener to events with the given name. It will be called when the event triggers.
- ...And the method `.off(name, handler)` that removes `handler` listener.
An event must have a name and, optionally, bundle some additional data. After adding the mixin, an object `user` will become able to 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.
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, 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. And so on.
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. Here's the code:
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 ```js run
let eventMixin = { let eventMixin = {
@ -146,7 +148,7 @@ let eventMixin = {
}, },
/** /**
* Generate the event and attach the data to it * Generate an event with the given name and data
* this.trigger('select', data1, data2); * this.trigger('select', data1, data2);
*/ */
trigger(eventName, ...args) { trigger(eventName, ...args) {
@ -160,12 +162,10 @@ let eventMixin = {
}; };
``` ```
There are 3 methods here:
1. `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name happens. The handlers are stored in the `_eventHandlers` property.
2. `.off(eventName, handler)` -- removes the function from the handlers list.
3. `.trigger(eventName, ...args)` -- generates the event: all assigned handlers are called and `args` are passed as arguments to them.
- `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name happens. Technically, there's `_eventHandlers` property, that stores an array of handlers for each event name. So it just adds it to the list.
- `.off(eventName, handler)` -- removes the function from the handlers list.
- `.trigger(eventName, ...args)` -- generates the event: all handlers from `_eventHandlers[eventName]` are called, with a list of arguments `...args`.
Usage: Usage:
@ -176,30 +176,31 @@ class Menu {
this.trigger("select", value); this.trigger("select", value);
} }
} }
// Add the mixin // Add the mixin with event-related methods
Object.assign(Menu.prototype, eventMixin); Object.assign(Menu.prototype, eventMixin);
let menu = new Menu(); let menu = new Menu();
// call the handler on selection: // add a handler, to be called on selection:
*!* *!*
menu.on("select", value => alert(`Value selected: ${value}`)); menu.on("select", value => alert(`Value selected: ${value}`));
*/!* */!*
// triggers the event => shows Value selected: 123 // triggers the event => the handler above runs and shows:
menu.choose("123"); // value selected // Value selected: 123
menu.choose("123");
``` ```
Now if we have the code interested to react on user selection, we can bind it with `menu.on(...)`. Now if we'd like any code to react on menu selection, we can listen to it with `menu.on(...)`.
And the `eventMixin` can add such behavior to as many classes as we'd like, without interfering with the inheritance chain. And `eventMixin` mixin makes it easy to add such behavior to as many classes as we'd like, without interfering with the inheritance chain.
## Summary ## Summary
*Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes. *Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes.
Some other languages like e.g. python allow to create mixins using multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying them into the prototype. Some other languages like e.g. Python allow to create mixins using multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype.
We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above. We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above.
Mixins may become a point of conflict if they occasionally overwrite native class methods. So generally one should think well about the naming for a mixin, to minimize such possibility. Mixins may become a point of conflict if they occasionally overwrite native class methods. So generally one should think well about the naming methods of a mixin, to minimize the probability of that.