work
|
@ -119,13 +119,16 @@ The `...rest` must always be the last.
|
|||
|
||||
````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
|
||||
function showName() {
|
||||
alert( arguments[0] );
|
||||
alert( arguments[1] );
|
||||
alert( arguments.length );
|
||||
|
||||
// for..of works too
|
||||
// for(let arg of arguments) alert(arg);
|
||||
}
|
||||
|
||||
// shows: Julius, Caesar, 2
|
||||
|
@ -135,7 +138,7 @@ showName("Julius", "Caesar");
|
|||
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
|
||||
|
@ -158,7 +161,7 @@ Like this?
|
|||
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!
|
||||
|
||||
|
@ -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.
|
||||
|
||||
## The spread operator
|
||||
## The spread operator [#spread-operator]
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
let fullName = ["Gaius", "Julius", "Caesar"];
|
||||
|
@ -256,7 +259,6 @@ function showName(firstName, secondName, lastName) {
|
|||
showName(...fullName);
|
||||
```
|
||||
|
||||
|
||||
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:
|
||||
|
@ -276,13 +278,14 @@ alert( Math.max(...arr) ); // 7
|
|||
|
||||
In short:
|
||||
- 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.
|
||||
|
||||
|
||||
## Summary TODO
|
||||
|
||||
[todo]
|
||||
Основные улучшения в функциях:
|
||||
|
||||
- Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Function expressions and more
|
||||
# Function expressions and arrows
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
## "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.
|
||||
|
||||
A more intricate call would lead to losing `this`, for instance:
|
||||
An intricate method call can loose `this`, for instance:
|
||||
|
||||
```js run
|
||||
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
|
||||
(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.
|
||||
- `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:
|
||||
|
||||
```js
|
||||
// base name strict
|
||||
// Reference Type value
|
||||
(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`.
|
||||
- Other operators just get `base[name]` and use it.
|
||||
Any other operation just gets `base[name]` value and uses it, discarding the reference type as a whole.
|
||||
|
||||
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]
|
||||
|
||||
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:
|
||||
|
||||
|
@ -291,25 +310,27 @@ sayHi.call( admin ); // Admin
|
|||
|
||||
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:
|
||||
```js
|
||||
func(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"
|
||||
|
||||
There's also a similar syntax:
|
||||
There's also a similar method `func.apply`:
|
||||
|
||||
```js
|
||||
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:
|
||||
|
||||
|
@ -318,9 +339,9 @@ func.call(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
|
||||
let args = [1, 2, 3];
|
||||
|
@ -329,7 +350,7 @@ func.call(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"
|
||||
|
||||
|
|
|
@ -163,6 +163,13 @@ alert( str[1000] ); // undefined
|
|||
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
|
||||
|
||||
|
@ -337,9 +344,9 @@ Just remember: `if (~str.indexOf(...))` reads as "if found".
|
|||
|
||||
### 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
|
||||
alert( "Widget with id".includes("Widget") ); // true
|
||||
|
@ -347,6 +354,13 @@ alert( "Widget with id".includes("Widget") ); // true
|
|||
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:
|
||||
|
||||
```js run
|
||||
|
@ -354,7 +368,6 @@ alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid"
|
|||
alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"
|
||||
```
|
||||
|
||||
|
||||
## Getting a substring
|
||||
|
||||
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`.
|
||||
|
||||
`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:
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
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]
|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
|
||||
## Object literals
|
||||
|
||||
An empty object ("empty cabinet") can be created using one of two syntaxes:
|
||||
|
||||
```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"
|
||||
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.
|
||||
````
|
||||
|
||||
## 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
|
||||
|
@ -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.
|
||||
|
||||
### 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
|
||||
let a = {};
|
||||
|
@ -426,9 +433,9 @@ let b = {}; // two independents object
|
|||
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?
|
||||
|
||||
|
|
|
@ -9,10 +9,11 @@ What this code is going to show?
|
|||
```js
|
||||
let fruits = ["Apples", "Pear", "Orange"];
|
||||
|
||||
// push a new value into the "copy"
|
||||
let shoppingCart = fruits;
|
||||
|
||||
shoppingCart.push("Banana");
|
||||
|
||||
// what's in fruits?
|
||||
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.
|
||||
|
||||
|
||||
````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"
|
||||
|
||||
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.
|
||||
|
||||
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 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.
|
||||
|
||||
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:
|
||||
|
||||
- `map.keys()` -- returns an array-like object for keys,
|
||||
- `map.values()` -- returns an array-like object for values,
|
||||
- `map.entries()` -- returns an array-like object for entries `[key, value]`, it's used by default in `for..of`.
|
||||
- `map.keys()` -- returns an iterable object for keys,
|
||||
- `map.values()` -- returns an iterable object for values,
|
||||
- `map.entries()` -- returns an iterable object for entries `[key, value]`, it's used by default in `for..of`.
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -182,7 +182,7 @@ recipeMap.forEach( (value, key, map) => {
|
|||
|
||||
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.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`.
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
`WeakSet` is a special kind of `Set` that does not prevent JavaScript from memory cleaning. `WeakMap` is the same thing for `Map`.
|
||||
|
|
|
@ -22,7 +22,7 @@ There are two ways to solve it.
|
|||
|
||||
1. Iterative thinking -- the `for` loop:
|
||||
|
||||
```js run
|
||||
```js run
|
||||
function pow(x, n) {
|
||||
let result = 1;
|
||||
|
||||
|
@ -132,7 +132,8 @@ function pow(x, n) {
|
|||
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">
|
||||
<li>
|
||||
|
|
|
@ -8,16 +8,16 @@
|
|||
.function-execution-context {
|
||||
border: 1px solid black;
|
||||
font-family: "Consolas", monospace;
|
||||
padding: 5px 8px;
|
||||
padding: 4px 6px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.function-execution-context-call {
|
||||
color: gray;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.function-execution-context-call::before {
|
||||
content: '@';
|
||||
content: ' call: ';
|
||||
}
|
||||
|
||||
.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.
|
||||
|
||||
## Code blocks and loops
|
||||
## Code blocks and loops, IIFE
|
||||
|
||||
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`.
|
||||
|
||||
**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.
|
||||
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
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
|
||||
for(let i = 0; i < 10; i++) {
|
||||
// Each loop has its own Lexical Environment
|
||||
// {i: value}
|
||||
}
|
||||
(function() {
|
||||
|
||||
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"
|
||||
|
||||
In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration:
|
||||
|
|
|
@ -63,6 +63,9 @@ closures
|
|||
counter object?
|
||||
|
||||
new function
|
||||
|
||||
|
||||
|
||||
bind, currying
|
||||
decorators
|
||||
constructors
|
||||
|
|