work
|
@ -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]
|
||||||
Основные улучшения в функциях:
|
Основные улучшения в функциях:
|
||||||
|
|
||||||
- Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта.
|
- Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
19
1-js/2-first-steps/20-object-methods/2-check-syntax/task.md
Normal 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 :)
|
22
1-js/2-first-steps/20-object-methods/3-why-this/solution.md
Normal 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`.
|
||||||
|
|
26
1-js/2-first-steps/20-object-methods/3-why-this/task.md
Normal 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
|
||||||
|
```
|
||||||
|
|
|
@ -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 `.`.
|
||||||
|
|
||||||
|
|
|
@ -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?
|
||||||
|
```
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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() );
|
||||||
|
```
|
||||||
|
|
24
1-js/2-first-steps/20-object-methods/7-calculator/task.md
Normal 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]
|
||||||
|
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
39
1-js/2-first-steps/20-object-methods/8-chain-calls/task.md
Normal 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.
|
|
@ -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:
|
||||||
|
|
||||||
|
@ -291,25 +310,27 @@ 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"
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 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 = {
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
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?
|
||||||
|
|
||||||
|
|
|
@ -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 ); // ?
|
||||||
```
|
```
|
||||||
|
|
15
1-js/4-data-structures/6-array/3-call-array-this/solution.md
Normal 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.
|
18
1-js/4-data-structures/6-array/3-call-array-this/task.md
Normal 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](); // ?
|
||||||
|
```
|
||||||
|
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
@ -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.
|
Before Width: | Height: | Size: 6 KiB After Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
@ -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;
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -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['взо'] = 'ЗОВ'`.
|
|
||||||
|
|
||||||
Обратите внимание, ключ -- отсортирован, а само слово -- в исходной форме, чтобы можно было потом получить его из объекта.
|
|
||||||
|
|
||||||
Вторая запись по тому же ключу перезапишет первую, то есть в объекте останется ровно одно слово с таким набором букв.
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
importance: 3
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Отфильтровать анаграммы
|
|
||||||
|
|
||||||
*Анаграммы* -- слова, состоящие из одинакового количества одинаковых букв, но в разном порядке.
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```
|
|
||||||
воз - зов
|
|
||||||
киборг - гробик
|
|
||||||
корсет - костер - сектор
|
|
||||||
```
|
|
||||||
|
|
||||||
Напишите функцию `aclean(arr)`, которая возвращает массив слов, очищенный от анаграмм.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var arr = ["воз", "киборг", "корсет", "ЗОВ", "гробик", "костер", "сектор"];
|
|
||||||
|
|
||||||
alert( aclean(arr) ); // "воз,киборг,корсет" или "ЗОВ,гробик,сектор"
|
|
||||||
```
|
|
||||||
|
|
||||||
Из каждой группы анаграмм должно остаться только одно слово, не важно какое.
|
|
||||||
|
|
227
1-js/4-data-structures/8-iterable/article.md
Normal 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.
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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) );
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -63,6 +63,9 @@ closures
|
||||||
counter object?
|
counter object?
|
||||||
|
|
||||||
new function
|
new function
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bind, currying
|
bind, currying
|
||||||
decorators
|
decorators
|
||||||
constructors
|
constructors
|
||||||
|
|