This commit is contained in:
Ilya Kantor 2016-07-17 16:53:32 +03:00
parent 776e83e1f1
commit 480e69b843
87 changed files with 1020 additions and 233 deletions

View file

@ -119,13 +119,16 @@ The `...rest` must always be the last.
````smart header="The `arguments` variable" ````smart header="The `arguments` variable"
In old times, there were no rest operator. But there was a special variable named `arguments` that contained all arguments by their index. It is still supported and can be used like this: In old times, there was no rest operator. But there is a special variable named `arguments` that contains all arguments by their index. It is still supported and can be used like this:
```js run ```js run
function showName() { function showName() {
alert( arguments[0] ); alert( arguments[0] );
alert( arguments[1] ); alert( arguments[1] );
alert( arguments.length ); alert( arguments.length );
// for..of works too
// for(let arg of arguments) alert(arg);
} }
// shows: Julius, Caesar, 2 // shows: Julius, Caesar, 2
@ -135,7 +138,7 @@ showName("Julius", "Caesar");
showName("Ilya"); showName("Ilya");
``` ```
The downside is that `arguments` looks like an array, but it's not. It does not support many useful array features. It mainly exists for backwards compatibility, usually the rest operator is better. The downside is that though `arguments` looks like an array, but it's not. It does not support many useful array methods that we'll study later, and if they're needed, then the rest operator should be used.
```` ````
## Destructuring parameters ## Destructuring parameters
@ -158,7 +161,7 @@ Like this?
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])
``` ```
That's ugly. And becomes unreadable if we deal with more parameters. That's ugly. And becomes unreadable when we deal with more parameters.
Destructuring comes to the rescue! Destructuring comes to the rescue!
@ -235,13 +238,13 @@ showMenu(); // Menu 100 200
In the code above, the whole arguments object is `{}` by default, so there's always something to destructurize. In the code above, the whole arguments object is `{}` by default, so there's always something to destructurize.
## The spread operator ## The spread operator [#spread-operator]
As we've seen before, the rest operator `...` allows to gather parameters in the array. As we've seen before, the rest operator `...` allows to gather parameters in the array.
But there's a reverse named "the spread operator". It also looks like `...` and works at call-time. But there's a reverse named "the spread operator". It also looks like `...` and works at call-time.
The spread operator allows to "unfurl" an array into a list of parameters, like this: The spread operator allows to convert an array into a list of parameters, like this:
```js run ```js run
let fullName = ["Gaius", "Julius", "Caesar"]; let fullName = ["Gaius", "Julius", "Caesar"];
@ -256,7 +259,6 @@ function showName(firstName, secondName, lastName) {
showName(...fullName); showName(...fullName);
``` ```
Let's see a more real-life example. Let's see a more real-life example.
There exist a built-in function [Math.max](mdn:js/Math/max) that takes a list of values and returns the greatest one: There exist a built-in function [Math.max](mdn:js/Math/max) that takes a list of values and returns the greatest one:
@ -276,13 +278,14 @@ alert( Math.max(...arr) ); // 7
In short: In short:
- When `...` occurs in function parameters, it's called a "rest operator" and gathers parameters into the array. - When `...` occurs in function parameters, it's called a "rest operator" and gathers parameters into the array.
- When `...` occurs in a function call, it's called a "spread operator" and unfurls an array into the list. - When `...` occurs in a function call, it's called a "spread operator" and converts an array into the list.
Together they help to travel between a list and an array of parameters with ease. Together they help to travel between a list and an array of parameters with ease.
## Summary TODO ## Summary TODO
[todo]
Основные улучшения в функциях: Основные улучшения в функциях:
- Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта. - Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта.

View file

@ -1,4 +1,4 @@
# Function expressions and more # Function expressions and arrows
In JavaScript, a function is a value. In JavaScript, a function is a value.

View file

@ -0,0 +1,43 @@
**Error**!
Try it:
```js run
let user = {
name: "John",
go: function() { alert(this.name) }
}
(user.go)() // error!
```
The error message in most browsers does not give understanding what went wrong.
**The error appears because a semicolon is missing after `user = {...}`.**
Javascript does not assume a semicolon before a bracket `(user.go)()`, so it reads the code like:
```js no-beautify
let user = { go:... }(user.go)()
```
Then we can also see that such a joint expression is syntactically a call of the object `{ go: ... }` as a function with the argument `(user.go)`. And that also happens on the same line with `let user`, so the `user` object has not yet even been defined, hence the error.
If we insert the semicolon, all is fine:
```js run
let user = {
name: "John",
go: function() { alert(this.name) }
}*!*;*/!*
(user.go)() // John
```
Please note that brackets around `(user.go)` do nothing here. Usually they setup the order of operations, but here the dot `.` works first anyway, so there's no effect. Only the semicolon thing matters.

View file

@ -0,0 +1,19 @@
importance: 2
---
# Syntax check
What is the resule of this code?
```js no-beautify
let user = {
name: "John",
go: function() { alert(this.name) }
}
(user.go)()
```
P.S. There's a pitfall :)

View file

@ -0,0 +1,22 @@
Here's the explanations.
1. That's a regular object method call.
2. The same, brackets do not change the order of operations here, the dot is first anyway.
3. Here we have a more complex call `(expression).method()`. The call works as if it were split into two lines:
```js no-beautify
f = obj.go; // calculate the expression
f(); // call what we have
```
Here `f()` is executed as a function, without `this`.
4. The similar thing as `(3)`, to the left of the dot `.` we have an expression.
To explain the behavior of `(3)` and `(4)` we need to recall that property accessors (dot or square brackets) return a value of the Reference Type.
Any operation on it except a method call (like assignment `=` or `||`) turns it into an ordinary value, which does not carry the information allowing to set `this`.

View file

@ -0,0 +1,26 @@
importance: 3
---
# Explain the value of "this"
In the code above we intend to call `user.go()` method 4 times in a row.
But calls `(1)` and `(2)` works differently from `(3)` and `(4)`. Why?
```js run no-beautify
let obj, method;
obj = {
go: function() { alert(this); }
};
obj.go(); // (1) [object Object]
(obj.go)(); // (2) [object Object]
(method = obj.go)(); // (3) undefined
(obj.go || obj.stop)(); // (4) undefined
```

View file

@ -0,0 +1,46 @@
**Answer: an error.**
Try it:
```js run
function makeUser() {
return {
name: "John",
ref: this
};
};
let user = makeUser();
alert( user.ref.name ); // Error: Cannot read property 'name' of undefined
```
That's because rules that set `this` do not look at object literals.
Here the value of `this` inside `makeUser()` is `undefined`, because it is called as a function, not as a method.
And the object literal itself has no effect on `this`. The value of `this` is one for the whole function, code blocks and object literals do not affect it.
So `ref: this` actually takes current `this` of the function.
Here's the opposite case:
```js run
function makeUser() {
return {
name: "John",
*!*
ref() {
return this;
}
*/!*
};
};
let user = makeUser();
alert( user.ref().name ); // John
```
Now it works, because `user.ref()` is a method. And the value of `this` is set to the object before dot `.`.

View file

@ -0,0 +1,23 @@
importance: 5
---
# Using "this" in object literal
Here the function `makeUser` returns an object.
What is the result of accessing its `ref`? Why?
```js
function makeUser() {
return {
name: "John",
ref: this
};
};
let user = makeUser();
alert( user.ref.name ); // What's the result?
```

View file

@ -0,0 +1,14 @@
let calculator = {
sum() {
return this.a + this.b;
},
mul() {
return this.a * this.b;
},
read() {
this.a = +prompt('a?', 0);
this.b = +prompt('b?', 0);
}
};

View file

@ -0,0 +1,28 @@
describe("calculator", function() {
context("when 2 and 3 entered", function() {
beforeEach(function() {
sinon.stub(window, "prompt");
prompt.onCall(0).returns("2");
prompt.onCall(1).returns("3");
calculator.read();
});
afterEach(function() {
prompt.restore();
});
it("the sum is 5", function() {
assert.equal(calculator.sum(), 5);
});
it("the multiplication product is 6", function() {
assert.equal(calculator.mul(), 6);
});
});
});

View file

@ -0,0 +1,23 @@
```js run demo
let calculator = {
sum() {
return this.a + this.b;
},
mul() {
return this.a * this.b;
},
read() {
this.a = +prompt('a?', 0);
this.b = +prompt('b?', 0);
}
};
calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );
```

View file

@ -0,0 +1,24 @@
importance: 5
---
# Create a calculator
Create an object `calculator` with three methods:
- `read()` prompts for two values and saves them as object properties.
- `sum()` returns the sum of saved values.
- `mul()` multiplies saved values and returns the result.
```js
let calculator = {
// ... your code ...
};
calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );
```
[demo]

View file

@ -0,0 +1,40 @@
The solution is to return the object itself from every call.
```js run
let ladder = {
step: 0,
up() {
this.step++;
*!*
return this;
*/!*
},
down() {
this.step--;
*!*
return this;
*/!*
},
showStep() {
alert( this.step );
*!*
return this;
*/!*
}
}
ladder.up().up().down().up().down().showStep(); // 1
```
We also can write a single call per line. For long chains it's more readable:
```js
ladder
.up()
.up()
.down()
.up()
.down()
.showStep(); // 1
```

View file

@ -0,0 +1,39 @@
importance: 2
---
# Chaining
There's a `ladder` object that allows to go up and down:
```js
let ladder = {
step: 0,
up() {
this.step++;
},
down() {
this.step--;
},
showStep: function() { // shows the current step
alert( this.step );
}
};
```
Now, if we need to make several calls in sequence, can do it like this:
```js
ladder.up();
ladder.up();
ladder.down();
ladder.showStep(); // 1
```
Modify the code of `up` and `down` to make the calls chainable, like this:
```js
ladder.up().up().down().showStep(); // 1
```
Such approach is widely used across Javascript libraries.

View file

@ -213,11 +213,9 @@ If you forget `use strict`, then you may see something like "window" in the exam
That's one of the odd things of the previous standard that `"use strict"` fixes. That's one of the odd things of the previous standard that `"use strict"` fixes.
``` ```
## "this" is for direct calls ## Reference Type
The value of `this` is only passed the right way if the function is called using a dot `'.'` or square brackets. An intricate method call can loose `this`, for instance:
A more intricate call would lead to losing `this`, for instance:
```js run ```js run
let user = { let user = {
@ -230,21 +228,37 @@ user.hi(); // John (the simple call works)
*!* *!*
// now let's call user.hi or user.bye depending on the name // now let's call user.hi or user.bye depending on the name
(user.name == "John" ? user.hi : user.bye)(); // undefined (user.name == "John" ? user.hi : user.bye)(); // Error!
*/!* */!*
``` ```
On the last line the method is retrieved during the execution of the ternary `?`, and immediately called. But `"this"` is lost, the result is not `"John"` how it should be. On the last line the method `user.hi` is retrieved during the execution of the ternary `?`, and immediately called with brackets `()`. But that doesn't work right. You can see that the call results in an error, cause the value of `"this"` inside the call becomes `undefined`.
If we want to understand why it happens -- the reason is in the details of how `obj.method()` works. If we want to understand why it happens -- the reason is in the details of how `obj.method()` call works.
The method call has two independant operations in it: a dot `'.'` to access the property and brackets `()` to execute it (assuming that's a function). The method call has two successive operations in it:
- the dot `'.'` retrieves the property
- brackets `()` execute it (assuming that's a function).
As we've already seen, the function is a value of its own. It does not memorize the object by itself. So to "carry" it to the brackets, Javascript uses a trick -- the dot `'.'` returns not a function, but a value of the special Reference Type. So, you might have already asked yourself, why does it work? That is, if we put these operations on separate lines, then `this` is guaranted to be lost:
The Reference Type is a "specification type". It does not exist in real, but used internally to explain how some language features work. ```js run
let user = {
name: "John",
hi() { alert(this.name); }
}
The value of the Reference Type is a tuple `(base, name, strict)`, where: let hi = user.hi;
hi(); // Error, because this is undefined
```
...But for a single-line call `user.hi()` all is fine.
That's because a function is a value of its own. It does not carry the object. So to pass it to the brackets, Javascript uses a trick -- the dot `'.'` returns not a function, but a value of the special [Reference Type](https://tc39.github.io/ecma262/#sec-reference-specification-type).
The Reference Type is a "specification type". It does not exist in real, but is used internally. Engines are not required to implement it, just to make sure that code works as described.
The value of the Reference Type is a combination `(base, name, strict)`, where:
- `base` is the object. - `base` is the object.
- `name` is the property. - `name` is the property.
@ -253,20 +267,25 @@ The value of the Reference Type is a tuple `(base, name, strict)`, where:
The result of a property access `'.'` is a value of the Reference Type. For `user.sayHi` in strict mode it is: The result of a property access `'.'` is a value of the Reference Type. For `user.sayHi` in strict mode it is:
```js ```js
// base name strict // Reference Type value
(user, "sayHi", true) (user, "sayHi", true)
``` ```
Any operation on the Reference Type immediately "resolves" it: Then, when brackets `()` are called on the Reference Type, they receive the full information about the object and it's method, and can set the right `this = base`.
- Brackets `()` get the property `base[name]` and execute it with `this = base`. Any other operation just gets `base[name]` value and uses it, discarding the reference type as a whole.
- Other operators just get `base[name]` and use it.
So any operation on the result of dot `'.'` except a direct call discards `this`. So any operation on the result of dot `'.'` except a direct call discards `this`.
That's why the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj[method]()` syntax (they do the same here).
## Explicit "this" with "call/apply" [#call-apply] ## Explicit "this" with "call/apply" [#call-apply]
We can call a function explicitly providing the value of `"this"`. The value of `this` does not have to come from the aforementioned rules.
We can explicitly set it to any object using `func.call`.
The syntax is: The syntax is:
@ -285,31 +304,33 @@ let user = { name: "John" };
let admin = { name: "Admin" }; let admin = { name: "Admin" };
// use call to pass different objects as "this" // use call to pass different objects as "this"
sayHi.call( user ); // John sayHi.call( user ); // John
sayHi.call( admin ); // Admin sayHi.call( admin ); // Admin
``` ```
The first parameter of `call` is the intended value of `"this"`, the latter are arguments. The first parameter of `call` is the intended value of `"this"`, the latter are arguments.
So `sayHi.call(admin)` runs the function `sayHi` with `this = admin`, hence `this.name` in it becomes `"Admin"`.
These calls are roughly equivalent: These calls are roughly equivalent:
```js ```js
func(1, 2, 3); func(1, 2, 3);
func.call(obj, 1, 2, 3) func.call(obj, 1, 2, 3)
``` ```
...Except that the `call` sets "this" of course! They both call `func` with arguments `1`, `2` and `3`. The only difference is that `call` also sets `"this"`.
That's handy when we want to use a function in the context of different objects, but do not want to actually assign it to them. The method `func.call` is used when we'd like to use a function in the context of different objects, but do not want to actually assign it to them. We'll see more examples of it soon.
### "func.apply" ### "func.apply"
There's also a similar syntax: There's also a similar method `func.apply`:
```js ```js
func.apply(context, args) func.apply(context, args)
``` ```
It does the same as `call`: executes the function providing `context` as `this`, but where `call` awaits a list of arguments, `apply` awaits a single array of arguments. It does the same as `call`: executes the function providing `context` as `this`, but where `call` awaits a list of arguments, `apply` awaits an array.
These two calls do the same: These two calls do the same:
@ -318,9 +339,9 @@ func.call(obj, 1, 2, 3);
func.apply(obj, [1, 2, 3]); func.apply(obj, [1, 2, 3]);
``` ```
In old times `apply` was more powerful, because it allows to form the array of arguments dynamically. In old times `apply` was more powerful, because it allows to create the array of arguments dynamically. Their number is not hardcoded at code-write time.
But in the modern language, we have the spread operator `'...'` and can use it to unfurl an array into the list of for `call`, so these two are equal: But in the modern language, we have the spread operator `'...'` and can use it to convert an array into a list of for `call`, so these two are equal:
```js ```js
let args = [1, 2, 3]; let args = [1, 2, 3];
@ -329,7 +350,7 @@ func.call(obj, ...args);
func.apply(obj, args); func.apply(obj, args);
``` ```
So the use of `apply` over `call` is mainly a metter of personal preference. And it's somewhat better optimized than the spread operator, because it exists longer. Nowadays the use of `apply` or `call` is mainly a metter of personal preference. But `apply` is somewhat better optimized in engines than the call + spread combination, because it exists longer. So it would execute a little bit faster.
## Binding "this" with "bind" ## Binding "this" with "bind"

View file

@ -163,6 +163,13 @@ alert( str[1000] ); // undefined
alert( str.charAt(1000) ); // '' (an empty string) alert( str.charAt(1000) ); // '' (an empty string)
``` ```
Also we can iterate over characters using `for..of`:
```js run
for(let char of "Hello") {
alert(char); // H,e,l,l,o (char becomes "H", then "e", then "l" etc)
}
```
## Strings are immutable ## Strings are immutable
@ -337,9 +344,9 @@ Just remember: `if (~str.indexOf(...))` reads as "if found".
### includes, startsWith, endsWith ### includes, startsWith, endsWith
The more modern method [str.includes(substr)](mdn:js/String/includes) returns `true/false` depending on whether `str` has `substr` as its part. The more modern method [str.includes(substr, pos)](mdn:js/String/includes) returns `true/false` depending on whether `str` has `substr` as its part.
It's the right choice if we need to test for the match, without the position: It's the right choice if we need to test for the match, but don't need its position:
```js run ```js run
alert( "Widget with id".includes("Widget") ); // true alert( "Widget with id".includes("Widget") ); // true
@ -347,6 +354,13 @@ alert( "Widget with id".includes("Widget") ); // true
alert( "Hello".includes("Bye") ); // false alert( "Hello".includes("Bye") ); // false
``` ```
The optional second argument of `str.includes` is the position to start searching from:
```js run
alert( "Midget".includes("id") ); // true
alert( "Midget".includes("id", 3) ); // false, from position 3 there is no "id"
```
The methods [str.startsWith](mdn:js/String/startsWith) and [str.endsWith](mdn:js/String/endsWith) do exactly what they say: The methods [str.startsWith](mdn:js/String/startsWith) and [str.endsWith](mdn:js/String/endsWith) do exactly what they say:
```js run ```js run
@ -354,7 +368,6 @@ alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid"
alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get" alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"
``` ```
## Getting a substring ## Getting a substring
There are 3 methods in JavaScript to get a substring: `substring`, `substr` and `slice`. There are 3 methods in JavaScript to get a substring: `substring`, `substr` and `slice`.
@ -561,7 +574,7 @@ Note that surrogate pairs did not exist at the time when Javascript was created,
We actually have a single symbol in each of the strings above, but the `length` shows the length of `2`. We actually have a single symbol in each of the strings above, but the `length` shows the length of `2`.
`String.fromCodePoint` and `str.codePointAt` are notable exceptions that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt). These methods are actually the same as `fromCodePoint/codePointAt`, but don't work with surrogate pairs. `String.fromCodePoint` and `str.codePointAt` are few rare methods that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt). These methods are actually the same as `fromCodePoint/codePointAt`, but don't work with surrogate pairs.
But, for instance, getting a symbol can be tricky, because surrogate pairs are treated as two characters: But, for instance, getting a symbol can be tricky, because surrogate pairs are treated as two characters:
@ -574,7 +587,52 @@ Note that pieces of the surrogate pair have no meaning without each other. So, t
How to solve this problem? First, let's make sure you have it. Not every project deals with surrogate pairs. How to solve this problem? First, let's make sure you have it. Not every project deals with surrogate pairs.
But if you do, then search the internet for libraries which implement surrogate-aware versions of `slice`, `indexOf` and other functions. Technically, surrogate pairs are detectable by their codes: the first character has the code in the interval of `0xD800..0xDBFF`, while the second is in `0xDC00..0xDFFF`. So if we see a character with the code, say, `0xD801`, then the next one must be the second part of the surrogate pair. Libraries rely on that to split stirngs right. Unfortunately, there's no single well-known library to advise yet. If you do, then there are some more tricks to deal with surrogates:
Use `for..of` to iterate over characters.
: The `for..of` loop respects surrogate pairs:
```js run
let str = '𝒳😂';
for(let char of str) { // loop over characters of str
alert(char); // 𝒳, and then 😂
}
```
Create an array of characters using `Array.from(str)`:
: Here's the trick:
```js run
let str = '𝒳😂';
// splits str into array of characters
let chars = Array.from(str);
alert(chars[0]); // 𝒳
alert(chars[1]); // 😂
alert(chars.length); // 2
```
The [Array.from](mdn:js/Array/from) takes an array-like object and turns it into a real array. It works with other array-like objects too.
Technically here it does the same as:
```js run
let str = '𝒳😂';
let chars = []; // Array.from internally does the same loop
for(let char of str) {
chars.push(char);
}
alert(chars);
```
...But is shorter.
There are probably libraries in the internet that build surrogate-aware versions of other string calls based on that.
Technically, surrogate pairs are also detectable by their codes: the first character has the code in the interval of `0xD800..0xDBFF`, while the second is in `0xDC00..0xDFFF`. So if we see a character with the code, say, `0xD801`, then the next one must be the second part of the surrogate pair.
### Diacritical marks ### Diacritical marks

View file

@ -7,17 +7,17 @@ Objects in JavaScript combine two functionalities.
Here we concentrate on the first part: using objects as a data store, and we will study it in-depth. That's the required base for studying the second part. Here we concentrate on the first part: using objects as a data store, and we will study it in-depth. That's the required base for studying the second part.
An [associative array](https://en.wikipedia.org/wiki/Associative_array), also called "a hash" or "a dictionary" -- is a data structure for storing arbitrary data in the key-value format. Let's recap what we know about objects and add a bit more.
[cut] [cut]
## Object literals
We can imagine it as a cabinet with signed files. Every piece of data is stored in it's file. It's easy to find a file by it's name or add/remove a file. We can imagine it as a cabinet with signed files. Every piece of data is stored in it's file. It's easy to find a file by it's name or add/remove a file.
![](object.png) ![](object.png)
## Object literals
An empty object ("empty cabinet") can be created using one of two syntaxes: An empty object ("empty cabinet") can be created using one of two syntaxes:
```js ```js
@ -41,6 +41,44 @@ let user = {
![](object-user-props.png) ![](object-user-props.png)
In real code we quite often want to create an object with a property from a variable.
For instance:
```js run
function makeUser(name, age) {
return {
name: name,
age: age;
}
}
let user = makeUser("John", 30);
alert(user.name); // John
```
There's a *property value shorthand* to make it shorter.
Instead of `name: name` we can just write `name`, like this:
```js
function makeUser(name, age) {
return {
name,
age;
}
}
```
We can also combine normal properties and shorthands:
```js
let user = {
name, // same as name:name
age: 30
};
```
````smart header="Trailing comma" ````smart header="Trailing comma"
The last property may end with a comma: The last property may end with a comma:
@ -221,37 +259,6 @@ In the code above, the property `obj.test` technically exists. So the `in` opera
Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code. Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code.
```` ````
## Property shorthands
There are two more syntax features to write a shorter code.
Property value shorthands
: To create a property from a variable:
```js
let name = "John";
// same as { name: name }
let user = { name };
```
If we have a variable and want to add a same-named property, that's the way to write it shorter.
```js
// can combine normal properties and shorthands
let user = { name, age: 30 };
```
Methods definitions
: For properties that are functions, we've already seen a shorter syntax.
```js
let user = {
sayHi() { // same as "sayHi: function()"
alert("Hello");
}
};
```
## Loops ## Loops
@ -402,9 +409,9 @@ alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference
Quite obvious, if we used one of the keys (`admin`) and changed something inside the cabinet, then if we use another key later (`user`), we find things modified. Quite obvious, if we used one of the keys (`admin`) and changed something inside the cabinet, then if we use another key later (`user`), we find things modified.
### Comparison with objects ### Comparison by reference
Two objects are equal only when they are one object: Two object variabls are equal only when reference the same object:
```js run ```js run
let a = {}; let a = {};
@ -426,9 +433,9 @@ let b = {}; // two independents object
alert( a == b ); // false alert( a == b ); // false
``` ```
For unusual equality checks like: object vs a priimtive, or an object less/greater `< >` than another object, objects are converted to numbers. To say the truth, such comparisons occur very rarely in real code and usually are a result of a mistake. For unusual equality checks like: object vs a primitive (`obj == 5`), or an object less/greater than another object (`obj1 > obj2`), objects are converted to numbers. To say the truth, such comparisons occur very rarely in real code and usually are a result of a coding mistake.
## Cloning, Object.assign ## Cloning and Object.assign
What if we need to duplicate an object? Create an independant copy, a clone? What if we need to duplicate an object? Create an independant copy, a clone?

View file

@ -9,10 +9,11 @@ What this code is going to show?
```js ```js
let fruits = ["Apples", "Pear", "Orange"]; let fruits = ["Apples", "Pear", "Orange"];
// push a new value into the "copy"
let shoppingCart = fruits; let shoppingCart = fruits;
shoppingCart.push("Banana"); shoppingCart.push("Banana");
// what's in fruits?
alert( fruits.length ); // ? alert( fruits.length ); // ?
``` ```

View file

@ -0,0 +1,15 @@
The call `arr[2]()` is syntactically the good old `obj[method]()`, in the role of `obj` we have `arr`, and in the role of `method` we have `2`.
So we have a call of the function `arr[2]` as an object method. Naturally, it receives `this` referencing the object `arr` and outputs the array:
```js run
let arr = ["a", "b"];
arr.push(function() {
alert( this );
})
arr[2](); // "a","b",function
```
The array has 3 values: initially it had two, plus the function.

View file

@ -0,0 +1,18 @@
importance: 5
---
# Calling in an array context
What is the result? Why?
```js
let arr = ["a", "b"];
arr.push(function() {
alert( this );
})
arr[2](); // ?
```

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

View file

@ -313,6 +313,24 @@ But that's actually a bad idea. There are potential problems with it:
So we should never use `for..in` for arrays. So we should never use `for..in` for arrays.
````smart header="`for..of` over `arr.entries()`"
For "real" arrays and some array-like structures, there's one more way:
```js run
let arr = ["Apple", "Orange", "Pear"];
*!*
for (let [i, item] of arr.entries()) {
*/!*
alert( i + ':' + item ); // 0:Apple, then 1:Orange, then 2:Pear
}
```
Here [arr.entries](mdn:js/Array/entries) is a built-in method, most array-like structures do not support that.
````
## A word about "length" ## A word about "length"
The `length` property automatically updates when we modify the array. It is actually not the *count* of values in the array, but the greatest numeric index plus one. The `length` property automatically updates when we modify the array. It is actually not the *count* of values in the array, but the greatest numeric index plus one.
@ -413,8 +431,9 @@ We can use an array as a deque with the following operations:
- `unshift(...items)` adds items to the beginning. - `unshift(...items)` adds items to the beginning.
To loop over the elements of the array: To loop over the elements of the array:
- `for(let item of arr)` -- the modern syntax,
- `for(let i=0; i<arr.length; i++)` -- works fastest, old-browser-compatible. - `for(let i=0; i<arr.length; i++)` -- works fastest, old-browser-compatible.
- `for(let item of arr)` -- the modern syntax for items only,
- `for(let [i,item] of arr.entries())` -- the modern syntax for indexes together with items,
- `for(let i in arr)` -- never use. - `for(let i in arr)` -- never use.
That were the "extended basics". There are more methods. In the next chapter we'll study them in detail. That were the "extended basics". There are more methods. In the next chapter we'll study them in detail.

View file

Before

Width:  |  Height:  |  Size: 6 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

View file

@ -1,16 +0,0 @@
function aclean(arr) {
var obj = {};
for (var i = 0; i < arr.length; i++) {
var sorted = arr[i].toLowerCase().split("").sort().join("");
obj[sorted] = arr[i];
}
var result = [];
for (var key in obj) {
result.push(obj[key]);
}
return result;
}

View file

@ -1,26 +0,0 @@
function intersection(arr1, arr2) {
return arr1.filter(function(item) {
return arr2.indexOf(item) != -1;
});
}
describe("aclean", function() {
it("содержит ровно по 1 слову из каждого набора анаграмм", function() {
var arr = ["воз", "киборг", "корсет", "зов", "гробик", "костер", "сектор"];
var result = aclean(arr);
assert.equal(result.length, 3);
assert.equal(intersection(result, ["гробик", "киборг"]).length, 1);
assert.equal(intersection(result, ["воз", "зов"]).length, 1);
assert.equal(intersection(result, ["корсет", "сектор", "костер"]).length, 1);
});
it("не различает регистр символов", function() {
var arr = ["воз", "ЗОВ"];
assert.equal(aclean(arr).length, 1);
});
});

View file

@ -1,69 +0,0 @@
# Решение
Чтобы обнаружить анаграммы, разобьём каждое слово на буквы и отсортируем их. В отсортированном по буквам виде все анаграммы одинаковы.
Например:
```
воз, зов -> взо
киборг, гробик -> бгикор
...
```
По такой последовательности будем делать массив уникальным.
Для этого воспользуемся вспомогательным объектом, в который будем записывать слова по отсортированному ключу:
```js run
function aclean(arr) {
// этот объект будем использовать для уникальности
var obj = {};
for (var i = 0; i < arr.length; i++) {
// разбить строку на буквы, отсортировать и слить обратно
*!*
var sorted = arr[i].toLowerCase().split('').sort().join(''); // (*)
*/!*
obj[sorted] = arr[i]; // сохраняет только одно значение с таким ключом
}
var result = [];
// теперь в obj находится для каждого ключа ровно одно значение
for (var key in obj) result.push(obj[key]);
return result;
}
var arr = ["воз", "киборг", "корсет", "ЗОВ", "гробик", "костер", "сектор"];
alert( aclean(arr) );
```
Приведение слова к сортированному по буквам виду осуществляется цепочкой вызовов в строке `(*)`.
Для удобства комментирования разобьём её на несколько строк (JavaScript это позволяет):
```js
var sorted = arr[i] // ЗОВ
.toLowerCase() // зов
.split('') // ['з','о','в']
.sort() // ['в','з','о']
.join(''); // взо
```
Получится, что два разных слова `'ЗОВ'` и `'воз'` получат одинаковую отсортированную форму `'взо'`.
Следующая строка:
```js
obj[sorted] = arr[i];
```
В объект `obj` будет записано сначала первое из слов `obj['взо'] = "воз"`, а затем `obj['взо'] = 'ЗОВ'`.
Обратите внимание, ключ -- отсортирован, а само слово -- в исходной форме, чтобы можно было потом получить его из объекта.
Вторая запись по тому же ключу перезапишет первую, то есть в объекте останется ровно одно слово с таким набором букв.

View file

@ -1,27 +0,0 @@
importance: 3
---
# Отфильтровать анаграммы
*Анаграммы* -- слова, состоящие из одинакового количества одинаковых букв, но в разном порядке.
Например:
```
воз - зов
киборг - гробик
корсет - костер - сектор
```
Напишите функцию `aclean(arr)`, которая возвращает массив слов, очищенный от анаграмм.
Например:
```js
var arr = ["воз", "киборг", "корсет", "ЗОВ", "гробик", "костер", "сектор"];
alert( aclean(arr) ); // "воз,киборг,корсет" или "ЗОВ,гробик,сектор"
```
Из каждой группы анаграмм должно остаться только одно слово, не важно какое.

View file

@ -0,0 +1,227 @@
# Iterables
*Iterable* objects is a general concept that allows to make any object to be useable in a `for..of` loop.
Many built-ins are partial cases of this concept. For instance, arrays are iterable. But not only arrays. Strings are iterable too.
Iterables come from the very core of Javascript and are widely used both in built-in methods and those provided by the environment. For instance, in-browser lists of DOM-nodes are iterable.
[cut]
## Symbol.iterator
We can easily grasp the concept of iterables by making one of our own.
For instance, we have an object, that is not an array, but looks a suitable for `for..of`.
Like a `range` object that represents an interval of numbers:
```js
let range = {
from: 1,
to: 5
};
// We want for..of to work:
// for(let num of range) ... num=1,2,3,4,5
```
To make the `range` iterable (and enable `for..of`) we need to add a method to the object with the name `Symbol.iterator` (a special built-in symbol just for that).
- When `for..of` starts, it calls that method (or errors if none found).
- The method must return an *iterator* -- an object with the method `next`.
- When `for..of` wants the next value, it calls `next()` on that object.
- The result of `next()` must have the form `{done: Boolean, value: any}`: `done:true` means that the iteration is finished, otherwise `value` must be the new value.
Let's see how it can work for `range`:
```js run
let range = {
from: 1,
to: 5
}
// call to for..of initially calls this
range[Symbol.iterator] = function() {
// ...it returns the iterator:
return {
current: this.from,
last: this.to,
// ...whose next() is called on each iteration of the loop
next() {
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true};
}
}
};
};
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
alert(Math.max(...range)); // 5 (*)
```
There is an important separation of concerns in this code.
- The `range` itself does not have the `next()` method.
- Instead, another object, a so-called "iterator" is created by the call to `range[Symbol.iterator]()`.
- It keeps the iteration state in its `current` property. That's good, because the original object is not modified by iterations. Also multiple `for..of` loops over the same object can run simultaneously, because they create separate iterators.
The fact that the object itself does not do the iteration also adds flexibility, because `range[Symbol.iterator]` can create iterators the smart way, depending on other object properties or external conditions. It's a full-fledged function that may be more complex than just a `return {...}`.
Please note that the internal mechanics is not seen from outside. Here `for..of` calls `range[Symbol.iterator]()` and then the `next()` until `done: false`, but the external code doesn't see that. It only gets values.
```smart header="Spread operator `...` and iterables"
At the last line `(*)` of the example above, we can see that an iterable object can be expanded to a list through a spread operator.
The call to `Math.max(...range)` internally does a full `for..of`-like iteration over `range`, and its results are used as a list of arguments for `Math.max`, in our case `Math.max(1,2,3,4,5)`.
```
```smart header="Infinite iterators"
Infinite iterators are also doable. For instance, the `range` becomes infinite for `range.to = Infinity`. Or we can make an iterable object that generates an infinite sequence of pseudorandom numbers. Also can be useful.
There are no limitations on `next`, it can return more and more values, that's normal.
Of course, the `for..of` loop over such an iterable would be endless, we'll need to stop if, for instance, using `break`.
```
````smart header="`Symbol.iterator` in a literal"
We could also write `Symbol.iterator` directly in the object literal, via computed properties syntax:
```js
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
return {...};
}
};
```
````
## Built-in iterables
Iterators can also be created explicitly, without `for..of`, with a direct call of `Symbol.iterator`. For built-in objects too.
For instance, this code gets a string iterator and calls it "manually":
```js run
let str = "Hello";
// does the same as
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();
while(true) {
let result = iterator.next();
if (result.done) break;
alert(result.value); // outputs characters one by one
}
```
The same works for an array.
## Iterables VS array-likes
There are two official terms that are similar, but actually very different. Please take care to avoid the confusion.
- Iterables are objects that implement the `Symbol.iterator` method, as described above.
- Array-likes are objects that have indexes and `length`, so they look like arrays.
Sometimes they can both be applied. For instance, strings are both iterable and array-like.
But an iterable may be not array-like and vise versa.
For example, the `range` in the example above is not array-like, because it does not have `length`.
And here's the object that is array-like, but not iterable:
```js run
let arrayLike = { // has indexes and length => array-like
0: "Hello",
1: "World",
length: 2
};
*!*
// Error (not iterable)
for(let item of arrayLike) {}
*/!*
```
...But what they share in common -- both iterables and array-likes are usually not arrays, they don't have `join`, `slice` etc. Or maybe have other methods that have same names, but behave differently from their `Array` counterparts.
Remember the special `arguments` object for a function call? It is both array-like and iterable object that has all function arguments:
```js run
function f() {
// array-like demo
alert( arguments[0] ); // 1
alert( arguments.length ); // 2
// iterable demo
for(let arg of arguments) alert(arg); // 1, then 2
}
f(1, 2);
```
...But as it's not an array, the following would fail:
```js run
function f() {
*!*
alert( arguments.join() ); // Error: arguments.join is not a function
*/!*
}
f(1, 2);
```
There's a universal method [Array.from](mdn:js/Array/from) that brings them together. It takes an iterable *or* an array-like value and makes a "real" `Array` from it.
For instance:
```js run
let arrayLike = {
0: "Hello",
1: "World",
length: 2
};
alert( Array.from(arrayLike).join() ); // Hello,World
```
```js
// assuming range is taken from the example above
alert( Array.from(range) ); // 1,2,3,4,5
```
This method is really handy when we want to use array methods like `map`, `forEach` and others on array-likes or iterables.
## Summary
Objects that can be used in `for..of` are called *iterable*.
- Technically, iterables must implement the method named `Symbol.iterator`.
- The result of `obj[Symbol.iterator]` is called an *iterator*. It handles the further iteration process.
- An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the iteration end, otherwise the `value` is the next value.
- The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly.
- Built-in iterables like strings or arrays, also implement `Symbol.iterator`.
The modern specification mostly uses iterables instead of arrays where an ordered collection is required, because they are more abstract. For instance, the spread operator `"..."` does it.
Objects that have indexed properties and `length` are called *array-like*. Such objects may also have other properties and methods, but lack built-in methods of arrays.
`Array.from(obj)` makes a real `Array` of an iterable or array-like `obj`, and then we can use array methods on it.

View file

@ -0,0 +1,11 @@
function aclean(arr) {
let map = new Map();
for(let word of arr) {
let sorted = word.toLowerCase().split("").sort().join("");
map.set(sorted, word);
}
return Array.from(map.values());
}

View file

@ -0,0 +1,24 @@
function intersection(arr1, arr2) {
return arr1.filter(item => arr2.includes(item));
}
describe("aclean", function() {
it("returns exactly 1 word from each anagram set", function() {
let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];
let result = aclean(arr);
assert.equal(result.length, 3);
assert.equal(intersection(result, ["nap", "PAN"]).length, 1);
assert.equal(intersection(result, ["teachers", "cheaters", "hectares"]).length, 1);
assert.equal(intersection(result, ["ear", "era"]).length, 1);
});
it("is case-insensitive", function() {
let arr = ["era", "EAR"];
assert.equal(aclean(arr).length, 1);
});
});

View file

@ -0,0 +1,81 @@
To find all anagrams, let's split every word to letters and sort them. When letter-sorted, all anagrams are same.
For instance:
```
nap, pan -> anp
ear, era, are -> aer
cheaters, hectares, teachers -> aceehrst
...
```
We'll use the letter-sorted variants as map keys to store only one value per each key:
```js run
function aclean(arr) {
let map = new Map();
for(let word of arr) {
// split the word by letters, sort them and join back
*!*
let sorted = word.toLowerCase().split('').sort().join(''); // (*)
*/!*
map.set(sorted, word);
}
return Array.from(map.values());
}
let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];
alert( aclean(arr) );
```
Letter-sorting is done by the chain of calls in the line `(*)`.
For convenience let's split it into multiple lines:
```js
var sorted = arr[i] // PAN
.toLowerCase() // pan
.split('') // ['p','a','n']
.sort() // ['a','n','p']
.join(''); // anp
```
Two different words `'PAN'` and `'nap'` receive the same letter-sorted form `'anp'`.
The next line put the word into the map:
```js
map.set(sorted, word);
```
If we ever meet a word the same letter-sorted form again, then it would overwrite the previous value with the same key in the map. So we'll always have at maximum one word per letter-form.
At the end `Array.from(map.values())` takes an iterable over map values (we don't need keys in the result) and returns an array of them.
Here we could also use a plain object instead of the `Map`, because keys are strings.
That's how the solution can look:
```js run
function aclean(arr) {
var obj = {};
for (var i = 0; i < arr.length; i++) {
var sorted = arr[i].toLowerCase().split("").sort().join("");
obj[sorted] = arr[i];
}
return Array.from(Object.values(obj));
}
let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];
alert( aclean(arr) );
```

View file

@ -0,0 +1,28 @@
importance: 4
---
# Filter anagrams
[Anagrams](https://en.wikipedia.org/wiki/Anagram) are words that have the same number of same letters, but in different order.
For instance:
```
nap - pan
ear - are - era
cheaters - hectares - teachers
```
Write a function `aclean(arr)` that returns an array cleaned from anagrams.
For instance:
```js
let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];
alert( aclean(arr) ); // "nap,teachers,ear" or "PAN,cheaters,era"
```
From every anagram group should remain only one word, no matter which one.

View file

@ -134,9 +134,9 @@ Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"],
For looping over a `map`, there are 3 methods: For looping over a `map`, there are 3 methods:
- `map.keys()` -- returns an array-like object for keys, - `map.keys()` -- returns an iterable object for keys,
- `map.values()` -- returns an array-like object for values, - `map.values()` -- returns an iterable object for values,
- `map.entries()` -- returns an array-like object for entries `[key, value]`, it's used by default in `for..of`. - `map.entries()` -- returns an iterable object for entries `[key, value]`, it's used by default in `for..of`.
For instance: For instance:
@ -182,7 +182,7 @@ recipeMap.forEach( (value, key, map) => {
The main methods are: The main methods are:
- `new Set([values])` -- creates the set, optionally with an array of values (any iterable will do). - `new Set(iterable)` -- creates the set, optionally from an array of values (any iterable will do).
- `set.add(value)` -- adds a value, returns the set itself. - `set.add(value)` -- adds a value, returns the set itself.
- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. - `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`.
- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. - `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`.
@ -236,6 +236,12 @@ Note the funny thing. The `forEach` function in the `Set` has 3 arguments: a val
That's made for compatibility with `Map` where `forEach` has three arguments. That's made for compatibility with `Map` where `forEach` has three arguments.
The same methods as `Map` has for iterators are also supported:
- `set.keys()` -- returns an iterable object for values,
- `set.values()` -- same as `set.keys`, for compatibility with `Map`,
- `set.entries()` -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`.
## WeakMap and WeakSet ## WeakMap and WeakSet
`WeakSet` is a special kind of `Set` that does not prevent JavaScript from memory cleaning. `WeakMap` is the same thing for `Map`. `WeakSet` is a special kind of `Set` that does not prevent JavaScript from memory cleaning. `WeakMap` is the same thing for `Map`.

View file

@ -22,7 +22,7 @@ There are two ways to solve it.
1. Iterative thinking -- the `for` loop: 1. Iterative thinking -- the `for` loop:
```js run ```js run
function pow(x, n) { function pow(x, n) {
let result = 1; let result = 1;
@ -132,7 +132,8 @@ function pow(x, n) {
alert( pow(2, 3) ); alert( pow(2, 3) );
``` ```
The line changes, so the context is now:
The variables are same, but the line changes, so the context is now:
<ul class="function-execution-context-list"> <ul class="function-execution-context-list">
<li> <li>

View file

@ -8,16 +8,16 @@
.function-execution-context { .function-execution-context {
border: 1px solid black; border: 1px solid black;
font-family: "Consolas", monospace; font-family: "Consolas", monospace;
padding: 5px 8px; padding: 4px 6px;
margin: 0 4px;
} }
.function-execution-context-call { .function-execution-context-call {
color: gray; color: gray;
padding-left: 20px;
} }
.function-execution-context-call::before { .function-execution-context-call::before {
content: '@'; content: ' call: ';
} }
.function-execution-context-list li:first-child { .function-execution-context-list li:first-child {

View file

@ -373,7 +373,7 @@ alert( counter() ); // 10
Sometimes such possibility can be a plus, but usually we want more control over `count`, and the other way is prefered. Sometimes such possibility can be a plus, but usually we want more control over `count`, and the other way is prefered.
## Code blocks and loops ## Code blocks and loops, IIFE
A code block has it's own Lexical Environment and hence local variables. A code block has it's own Lexical Environment and hence local variables.
@ -400,7 +400,16 @@ The new Lexical Environment gets the enclosing one as the outer reference, so `p
After `if` finishes, its Lexical Environment is normally destroyed (unless there's a living nested function). That's why the `alert` below won't see the `user`. After `if` finishes, its Lexical Environment is normally destroyed (unless there's a living nested function). That's why the `alert` below won't see the `user`.
**We also can use a "bare" code block to isolate variables.** For a loop, every run has a separate Lexical Environment. The loop variable is its part:
```js run
for(let i = 0; i < 10; i++) {
// Each loop has its own Lexical Environment
// {i: value}
}
```
We also can use a "bare" code block to isolate variables.
For instance, in-browser all scripts share the same global area. So if we create a global variable in one script, it becomes available to others. That may be a source of conflicts if two scripts use the same variable name and overwrite each other. For instance, in-browser all scripts share the same global area. So if we create a global variable in one script, it becomes available to others. That may be a source of conflicts if two scripts use the same variable name and overwrite each other.
@ -418,15 +427,61 @@ If we don't want that, we can use a code block to isolate the whole script or an
alert(message); // Error: message is not defined alert(message); // Error: message is not defined
``` ```
For a loop, every run has a separate Lexical Environment. The loop variable is its part: In old scripts, you can find immediately-invoked function expressions (abbreviated as IIFE) used for this purpose.
They look like this:
```js run ```js run
for(let i = 0; i < 10; i++) { (function() {
// Each loop has its own Lexical Environment
// {i: value} let message = "Hello";
}
alert(message); // Hello
})();
``` ```
Here a Function Expression is created and immediately called. So the code executes right now and has its own private variables.
The Function Expression is wrapped with brackets `(function {...})`, because otherwise Javascript would try to read it as Function Declaration:
```js run
// Error: Unexpected token (
function() { // <-- JavaScript assumes it is a Function Declarations, but no name
let message = "Hello";
alert(message); // Hello
}();
```
...And we can't actually use Function Declaration here, because Javascript does not allow them to be called immediately:f
```js run
// syntax error
function go() {
}();
```
So the brackets are needed to show Javascript that we make a function in the context of another expression. Other means to do that:
```js run
!function() {
alert("Bitwise NOT operator starts the expression");
}();
+function() {
alert("Unary plus starts the expression");
}();
```
## The old "var" ## The old "var"
In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration: In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration:

View file

@ -63,6 +63,9 @@ closures
counter object? counter object?
new function new function
bind, currying bind, currying
decorators decorators
constructors constructors