This commit is contained in:
Ilya Kantor 2016-07-22 20:47:41 +03:00
parent 057783d216
commit 3f5f2cac8b
324 changed files with 669 additions and 286 deletions

View file

@ -2,7 +2,7 @@
In Javascript a function is not a "magical language structure", but a special kind of value. In Javascript a function is not a "magical language structure", but a special kind of value.
The syntax that we've used before is called *Function Declaration*: The syntax that we used before is called *Function Declaration*:
```js ```js
function sayHi() { function sayHi() {
@ -87,8 +87,8 @@ let sayHi = function() {
``` ```
The answer is simple: The answer is simple:
- There's no need in `;` at the end of code blocks and syntax structures that use them like `if { ... }`, `for { }`, `function f { }` and alike. - There's no need in `;` at the end of code blocks and syntax structures that use them like `if { ... }`, `for { }`, `function f { }` etc.
- A Function Expression appears in the context of the statement: `let sayHi = value;`. It's not a code block. The semicolon `;` is recommended at the end of statements, no matter what is the `value`. So the semicolon here is not related to Function Expression itself in any way, it just terminates the statement. - The Function Expression appears in the context of the statement: `let sayHi = value;`. It's not a code block. The semicolon `;` is recommended at the end of statements, no matter what is the `value`. So the semicolon here is not related to Function Expression itself in any way, it just terminates the statement.
```` ````

View file

@ -0,0 +1,11 @@
Just loop over the object and `return false` immediately if there's at least one property.
```js
function isEmpty(obj)
for(let key in obj) {
return false;
}
return true;
}
```

View file

@ -1,5 +1,4 @@
```js run ```js run
let salaries = { let salaries = {
John: 100, John: 100,

View file

@ -16,4 +16,4 @@ let salaries = {
Write the code to sum all salaries and store in the variable `sum`. Should be `390` in the example above. Write the code to sum all salaries and store in the variable `sum`. Should be `390` in the example above.
Use `for..in` loop to iterate over the object. If `salaries` is empty, then the result must be `0`.

View file

@ -44,7 +44,7 @@ In the `user` object, there are two properties:
1. The first property has the name `"name"` and the value `"John"`. 1. The first property has the name `"name"` and the value `"John"`.
2. The second one has the name `"age"` and the value `30`. 2. The second one has the name `"age"` and the value `30`.
The `user` object can be imagined as a cabinet with two signed files labelled "name" and "age". The resulting `user` object can be imagined as a cabinet with two signed files labelled "name" and "age".
![user object](object-user.png) ![user object](object-user.png)

View file

@ -157,23 +157,23 @@ Regularly the following "garbage collection" steps are performed:
For instance, if our object structure might look like this: For instance, if our object structure might look like this:
![](garbage-collection-0.png) ![](garbage-collection-1.png)
Then the first step marks the roots: Then the first step marks the roots:
![](garbage-collection-1.png) ![](garbage-collection-2.png)
Then their references are marked: Then their references are marked:
![](garbage-collection-2.png) ![](garbage-collection-3.png)
...And their refences, while possible: ...And their refences, while possible:
![](garbage-collection-3.png) ![](garbage-collection-4.png)
Now the objects that could not be visited in the process are considered unreachable and will be removed: Now the objects that could not be visited in the process are considered unreachable and will be removed:
![](garbage-collection-4.png) ![](garbage-collection-5.png)
That's the concept how garbage collection works. That's the concept how garbage collection works.
@ -182,10 +182,10 @@ Javascript engines apply many optimizations to it, to make it run faster and be
Some of the optimizations: Some of the optimizations:
- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". Many objects appear, do their job and die fast, so they can be cleaned up more aggressively. Those "new" that survive for long enough, become "old". - **Generational collection** -- objects are split into two sets: "new ones" and "old ones". Many objects appear, do their job and die fast, so they can be cleaned up more aggressively. Those "new" that survive for long enough, become "old".
- **Incremental collection** -- there may be many objects, if we try to clean up the whole object tree at once, it may take some time and introduce visible delays. So the engine tries to split the job into pieces. Then pieces are executed one at a time. That requires some extra bookkeeping between them. - **Incremental collection** -- there may be many objects, if we try to clean up the whole object tree at once, it may take some time and introduce visible delays. So the engine tries to split the job into pieces. Then pieces are executed one at a time. That requires some extra bookkeeping in-between to stay consistent.
- **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution. - **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution.
In-depth understanding of the algorithms is also possible, there's no magic, but it requires a lot of under-the-hood digging and -- what's more important, things change, there are ongoing efforts to optimize them. In-depth understanding of these optimization is also achievable, there's no magic, but it requires a lot of under-the-hood digging. Javascript engines implement them differently. And -- what's even more important, things change, so going that deep is recommended when you already know the language well and really need low-level optimizations for your code.
## Summary ## Summary
@ -199,6 +199,6 @@ Modern engines implement advanced algorithms of garbage collection.
If you are familiar with low-level programming, the more detailed information about V8 garbage collector is in the article [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). Also you'd better prepare yourself by learning how values are stored in V8. I'm saying: "V8", because it is best covered with articles in the internet. For other engines, things are partially similar, but not the same. If you are familiar with low-level programming, the more detailed information about V8 garbage collector is in the article [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). Also you'd better prepare yourself by learning how values are stored in V8. I'm saying: "V8", because it is best covered with articles in the internet. For other engines, things are partially similar, but not the same.
In-depth knowledge of engines is good when you need low-level optimizations. It would be a great next step after you're familiar with the language. In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language. And when you feel that you need that of course.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View file

@ -281,83 +281,6 @@ Any other operation like assignment `hi = user.hi` discards the reference type a
So, as the result, 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). So, as the result, 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]
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:
```js
func.call(context, arg1, arg2, ...)
```
For instance:
```js run
function sayHi() {
alert(this.name);
}
let user = { name: "John" };
let admin = { name: "Admin" };
// use call to pass different objects as "this"
sayHi.call( user ); // John
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)
```
They both call `func` with arguments `1`, `2` and `3`. The only difference is that `call` also sets `"this"`.
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 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 an array.
These two calls do the same:
```js
func.call(obj, 1, 2, 3);
func.apply(obj, [1, 2, 3]);
```
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 convert an array into a list of for `call`, so these two are equal:
```js
let args = [1, 2, 3];
func.call(obj, ...args);
func.apply(obj, args);
```
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"
There's still a way to bind "this" to a function.
[todo] migrate bind here????
## Summary ## Summary

View file

@ -23,10 +23,8 @@ Most of the time, it's more flexible and gives more readable code to explicitly
That said, there are still valid reasons why we should know how it works. That said, there are still valid reasons why we should know how it works.
- The `alert(user)` kind of output is still used for logging and debugging. - The `alert(user)` kind of output is still used for logging and debugging.
- The built-in `toString` method of objects allows to get the type of almost anything.
- Sometimes it just happens (on mistake?), and we should understand what's going on. - Sometimes it just happens (on mistake?), and we should understand what's going on.
## ToPrimitive ## ToPrimitive
When an object is used as a primitive, the special internal algorithm named [ToPrimitive](https://tc39.github.io/ecma262/#sec-toprimitive) is invoked. When an object is used as a primitive, the special internal algorithm named [ToPrimitive](https://tc39.github.io/ecma262/#sec-toprimitive) is invoked.
@ -167,73 +165,5 @@ if ({}) alert("true"); // works
That is not customizable. That is not customizable.
```` ````
## Bonus: toString for the type
There are many kinds of built-in objects in Javascript. Many of them have own implementations of `toString` and `valueOf`. We'll see them when we get to them.
But the built-in `toString()` of plain objects is also interesting, it does something very special.
From the first sight it's obvious:
```js run
let obj = { };
alert( obj ); // [object Object]
```
But it's much more powerful than that.
By [specification](https://tc39.github.io/ecma262/#sec-object.prototype.tostring), `toString` can work in the context of any value. And it returns `[object ...]` with the kind of the value inside.
The algorithm looks like this:
- If `this` value is `undefined`, return `[object Undefined]`
- If `this` value is `null`, return `[object Null]`
- If `this` is a function, return `[object Function]`
- ...Some other builtin cases for strings, numbers etc...
- Otherwise if there's a property `obj[Symbol.toStringTag]`, then return it inside `[object...]`.
- Otherwise, return `[object Object]`.
Most environment-specific objects even if they do not belong to Javascript core, like `window` in the browser or `process` in Node.JS, have `Symbol.toStringTag` property. So this algorithm works for them too.
To make use of it, we should pass the thing to examine as `this`. We can do it using [func.call](info:object-methods#call-apply).
```js run
let s = {}.toString; // copy toString of a plain object into a variable
// call the algorithm with this = null
alert( s.call(null) ); // [object Null]
// call the algorithm with this = alert
alert( s.call(alert) ); // [object Function]
// browser object works too
alert( s.call(window) ); // [object Window]
// (because it has Symbol.toStringTag)
alert( window[Symbol.toStringTag] ); // Window
```
There might be a question: "Why [object Null]?". Well, of course, `null` is not an object. The wrapper `[object ...]` is the same for historical reasons and for making things universal.
In the example above we copy the "original" `toString` method of a plain object to the variable `s`, and then use it to make sure that we use *exactly that* `toString`.
We could also call it directly:
```js run
alert( {}.toString.call("test") ); // [object String]
```
So, `{}.toString` can serve as "`typeof` on steroids" -- the more powerful version of type detection that not only distinguishes between basic language types, but also returns the kind of an object.
Most builtins have `Symbol.toStringTag` property, for our objects we can provide it too:
```js run
let user = {
[Symbol.toStringTag]: "User"
};
alert( {}.toString.call(user) ); // [object User]
```

View file

@ -431,7 +431,6 @@ alert( "1" + 1 ); // "11"
alert( "1,2" + 1 ); // "1,21" alert( "1,2" + 1 ); // "1,21"
``` ```
## Summary ## Summary
Array is a special kind of objects, suited to store and manage ordered data items. Array is a special kind of objects, suited to store and manage ordered data items.
@ -463,5 +462,5 @@ To loop over the elements of the array:
- `for(let item of arr)` -- the modern syntax for items only, - `for(let item of arr)` -- the modern syntax for items only,
- `for(let i in arr)` -- never use. - `for(let i in arr)` -- never use.
We will return to arrays and study more methods to add, remove, extract elements and sort arrays in the chapter <info:array-methods>.
That were the "extended basics". There are more methods, and in the future chapter <mdn:array-methods> we will return to them.

View file

@ -180,9 +180,28 @@ alert(arr.pop()); // World (method works)
```js ```js
// assuming that range is taken from the example above // assuming that range is taken from the example above
let arr = Array.from(range); let arr = Array.from(range);
alert(arr); // 1,2,3,4,5 (toString of Array gives a list of items) alert(arr); // 1,2,3,4,5
``` ```
The full syntax for `Array.from` allows to provide an optional "mapping" function:
```js
Array.from(obj[, mapFn, thisArg])
```
The second argument `mapFn` should be the function to apply to each element before adding to the array, and `thisArg` allows to set `this` for it.
For instance:
```js
// assuming that range is taken from the example above
// square each number
let arr = Array.from(range, num => num * num);
alert(arr); // 1,4,9,16,25
```
## Summary ## Summary
Objects that can be used in `for..of` are called *iterable*. Objects that can be used in `for..of` are called *iterable*.
@ -197,6 +216,6 @@ The modern specification mostly uses iterables instead of arrays where an ordere
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. 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. `Array.from(obj[, mapFn, thisArg])` makes a real `Array` of an iterable or array-like `obj`, and then we can use array methods on it. The optional arguments `mapFn` and `thisArg` allow to apply a function to each item.

View file

@ -0,0 +1,203 @@
# Function arguments: rest and spread
In Javascript, there is an internal type named [List](https://tc39.github.io/ecma262/#sec-list-and-record-specification-type).
When a function is called, it is said to have "a list of parameters". That's not a figure of speech. The specification officially says that in the call `f(a, b, c)` we have the function `f` the list of `(a, b, c)`.
[cut]
Both List and Array represent ordered collections of values.
The difference is:
- `Array` is the open data type that we can use, it provides special methods like `push/pop`.
- `List` is the internal type, to represend lists of arguments and such, it has no special methods.
There are two use cases when we want to convert between them:
1. To work with an arbitrary number of function arguments, it's useful to have them in the form of array.
2. We have an array of values and would like to call a function that requires them to be listed.
Both variants are possible.
## Rest parameters `...`
The rest parameters are denoted with three dots `...`. They literally mean: "gather the list into an array".
For instance, here we gather all arguments into array `args`:
```js run
function sumAll(...args) { // args is the name for the array
let sum = 0;
for(let arg of args) sum += arg;
return sum;
}
alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6
```
That also works partially.
For instance, here we put few first arguments into variables and gather only the rest of them:
```js run
function showName(firstName, lastName, ...rest) {
alert( firstName + ' ' + lastName ); // Julius Caesar
// the rest = ["Consul", "of the Roman Republic"]
alert( rest[0] ); // Consul
alert( rest[1] ); // of the Roman Republic
alert( rest.length ); // 2
}
showName("Julius", "Caesar", "Consul", "of the Roman Republic");
```
````warn header="The rest parameters must be at the end"
The rest parameters gather all remaining arguments, so the following has no sense:
```js
function f(arg1, ...rest, arg2) { // arg2 after ...rest ?!
// error
}
```
The `...rest` must always be the last.
````
## The "arguments" variable
But there is also a special array-like object named `arguments` that contains all arguments by their index.
For instance:
```js run
function showName() {
alert( arguments[0] );
alert( arguments[1] );
alert( arguments.length );
// iterable too
// for(let arg of arguments) alert(arg);
}
// shows: Julius, Caesar, 2
showName("Julius", "Caesar");
// shows: Ilya, undefined, 1
showName("Ilya");
```
In old times, rest parameters did not exist in the language, and `arguments` was the only way to get *all arguments* of the function no matter of their total number.
And it still works.
But the downside is that though `arguments` is both array-like and iterable, it's not an array. It does not support array methods. Also, it always has everything in it, we can't get first parameters in the variable and keep only the rest in arguments.
So when we need these features, then rest parameters are preferred.
## Spread operator [#spread-operator]
We've just seen how to get an array from the list of parameters.
Now let's do the reverse.
For instance, there's a built-in function [Math.max](mdn:js/Math/max) that returns the greatest number from the list:
```js run
alert( Math.max(3, 5, 1) ); // 5
```
Now let's say we have an array `[3, 5, 1]`. How to call `Math.max` with it?
Passing it "as it" won't work, because `Math.max` expects a list of numeric arguments, not a single array:
```js run
let arr = [3, 5, 1];
*!*
alert( Math.max(arr) ); // NaN
*/!*
```
Most of the time we also can't manually list items in the code `Math.max(arg[0], arg[1], arg[2])`, because their exact number is not known. As our script executes, there might be many, or there might be none.
*Spread operator* to the rescue. It looks similar to rest parameters, also using `...`, but does quite the opposite.
When `...iter` is used in the function call, it "expands" an iterable object `iter` into the list.
For `Math.max`:
```js run
let arr = [3, 5, 1];
alert( Math.max(...arr) ); // 5
```
Unlike rest parameters, there is no restrictions of now many iterables we use.
Here's example of a combination:
```js run
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
*!*
alert( Math.max(0, ...arr, 2, ...arr2) ); // 15
*/!*
```
```smart header="Spread or rest?"
When you see `"..."`, there's an easy way to differ spread operator from rest parameters:
- Rest parameters, if it's in the function definition (gathers into array).
- Spread operator, if anywhere else (expands an array).
```
Also we can use spread operator to merge arrays:
```js run
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
*!*
let merged = [0, ...arr, 2, ...arr2];
*/!*
alert(merged); // 0,3,5,1,2,8,9,15 (0, then arr, then 2, then arr2)
```
In the examples above we used an array, but any iterable will do including strings:
```js run
let str = "Hello";
alert( [...str] ); // H,e,l,l,o
```
The spread operator `...str` actually does the same as `for..of` to gather elements. For a string, `for..of` returns characters one by one. And then it passes them as a list to array initializer `["h","e","l","l","o"]`.
If our purpose is only to convert an iterable to array, then we can also use `Array.from`:
```js run
let str = "Hello";
// Array.from converts an iterable into an array
alert( Array.from(str) ); // H,e,l,l,o
```
## Summary
- When `...` occurs in function parameters, it's "rest parameters" and gathers the rest of the list into the array.
- When `...` occurs anywhere else, it's called a "spread operator" and expands an array into the list.
Together they help to travel between a list and an array of parameters with ease.
All arguments of a function call are also available in the `arguments` array-like iterable object.

View file

@ -1,6 +1,6 @@
# Destructuring # Destructuring assignment
Destructuring assignment is a special syntax for assignments that allows to immediately split an array or a complex object into variables. *Destructuring assignment* is a special syntax for assignments that allows to immediately split an array or a complex object into variables.
[cut] [cut]
@ -23,7 +23,7 @@ alert(surname); // Kantor
The destructuring assignment puts the first value of the array into the variable `firstName` and the second one into `surname`. Any other array elements (if exist) are ignored. The destructuring assignment puts the first value of the array into the variable `firstName` and the second one into `surname`. Any other array elements (if exist) are ignored.
Note that the array itself is not modified in the process. Note that the word "destructuring" may be a bit too fearful. It doesn't destroy anything. The array `arr` is not modified in the process. It just "unpacks" the array into variables.
````smart header="Ignore first elements" ````smart header="Ignore first elements"
Unwanted elements of the array can also be thrown away via an extra comma: Unwanted elements of the array can also be thrown away via an extra comma:
@ -42,7 +42,7 @@ In the code above, the first and second elements of the array are skipped, the t
### The rest operator ### The rest operator
If we want to get all following values of the array, but are not sure of their number -- we can add one more parameter that gets "the rest" using the rest operator `"..."` (three dots): If we want not just to get first values, but also to gather all that follows -- we can add one more parameter that gets "the rest" using the rest operator `"..."` (three dots):
```js run ```js run
*!* *!*
@ -51,13 +51,16 @@ let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Repub
alert(name1); // Julius alert(name1); // Julius
alert(name2); // Caesar alert(name2); // Caesar
*!*
alert(rest[0]); // Consul alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic alert(rest[1]); // of the Roman Republic
*/!*
``` ```
The value of `rest` is the array of the remaining array elements. We can use any other variable name in place of `rest`, the operator is three dots. It must be the last element of the destructuring assignment. The value of `rest` is the array of the remaining array elements. We can use any other variable name in place of `rest`, the operator is three dots. The rest operator must be the last in the destructuring assignment.
### The default values ### Default values
The there are less values in the array than variables in the assignment -- there will be no error, absent values are considered undefined: The there are less values in the array than variables in the assignment -- there will be no error, absent values are considered undefined:
@ -113,10 +116,10 @@ alert(width); // 100
alert(height); // 200 alert(height); // 200
``` ```
Properties `options.title`, `options.width` and `options.height` are automcatically assigned to the corresponding variables. The order of propertiies does not matter, that works too: Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables. The order does not matter, that works too:
```js ```js
// let {title, width, height} // changed the order of properties in let {...}
let {height, width, title} = { title: "Menu", height: 200, width: 100 } let {height, width, title} = { title: "Menu", height: 200, width: 100 }
``` ```
@ -130,10 +133,14 @@ let options = {
}; };
*!* *!*
// { source property: target variable } // { sourceProperty: targetVariable }
let {width: w, height: h, title} = options; let {width: w, height: h, title} = options;
*/!* */!*
// width -> w
// height -> h
// title -> title
alert(title); // Menu alert(title); // Menu
alert(w); // 100 alert(w); // 100
alert(h); // 200 alert(h); // 200
@ -141,7 +148,7 @@ alert(h); // 200
The colon shows "what : goes where". In the example above the property `width` goes to `w`, property `height` goes to `h`, and `title` is assigned to the same name. The colon shows "what : goes where". In the example above the property `width` goes to `w`, property `height` goes to `h`, and `title` is assigned to the same name.
If some properties are absent, we can set default values using `=`, like this: If some properties are absent, we can set default values using `"="`, like this:
```js run ```js run
let options = { let options = {
@ -188,7 +195,7 @@ let title, width, height;
{title, width, height} = {title: "Menu", width: 200, height: 100}; {title, width, height} = {title: "Menu", width: 200, height: 100};
``` ```
The problem is that Javascript treats `{...}` in the main code flow (not inside another expression) as a code block. Such standalone code blocks are rarely used, but in theory can group statements, like this: The problem is that Javascript treats `{...}` in the main code flow (not inside another expression) as a code block. Such standalone code blocks are rarely used, but can group statements, like this:
```js run ```js run
{ {
@ -199,13 +206,14 @@ The problem is that Javascript treats `{...}` in the main code flow (not inside
} }
``` ```
To show Javascript that it's not a code block, we can wrap the whole assignment in brackets `(...)`: To show Javascript that it's not a code block, but a something else, we can wrap the whole assignment in brackets `(...)`:
```js run ```js run
let title, width, height; let title, width, height;
// okay now // starts with a bracket, so it's not a code block
// now Javascript will see the destructuring assignment
*!*(*/!*{title, width, height} = {title: "Menu", width: 200, height: 100}*!*)*/!*; *!*(*/!*{title, width, height} = {title: "Menu", width: 200, height: 100}*!*)*/!*;
alert( title ); // Menu alert( title ); // Menu
@ -248,6 +256,103 @@ As we can see, the whole `options` object is correctly assigned to variables.
The left part of the destructuring assignment can combine and nest things as needed. The left part of the destructuring assignment can combine and nest things as needed.
## Destructuring function parameters
There are times when a function may have many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, items list and so on.
Here's a bad way to write such function:
```js
function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
// ...
}
```
The real-life problem is how to remember the order of arguments. Usually IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default.
Like this?
```js
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])
```
That's ugly. And becomes unreadable when we deal with more parameters.
Destructuring comes to the rescue!
We can pass parameters as an object, and the function immediately destructurizes them into variables:
```js run
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
*!*
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
*/!*
alert( title + ' ' + width + ' ' + height ); // My Menu 100 200
alert( items ); // Item1, Item2
}
showMenu(options);
```
We can also use the more complex destructuring with nestings and colon mappings:
```js run
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
*!*
function showMenu({
title = "Untitled",
width:w = 100, // width goes to w
height:h = 200, // height goes to h
items: [item1, item2] // items first element goes to item1, second to item2
}) {
*/!*
alert( title + ' ' + w + ' ' + h ); // My Menu 100 200
alert( item1 ); // Item1
alert( item2 ); // Item2
}
showMenu(options);
```
The syntax is the same as for a destructuring assignment:
```js
function({
incomingProperty: parameterName = defaultValue
...
})
```
Please note that such destructuring assumes that `showMenu()` does have an argument. If we want all values by default, then we should specify an empty object:
```js
showMenu({});
// that would give an error
showMenu();
```
We can fix this by making `{}` a value by default for the whole destructuring thing:
```js run
// simplified parameters a bit for clarity
function showMenu(*!*{ title="Menu", width=100, height=200 } = {}*/!*) {
alert( title + ' ' + width + ' ' + height );
}
showMenu(); // Menu 100 200
```
In the code above, the whole arguments object is `{}` by default, so there's always something to destructurize.
## Summary ## Summary
- Destructuring assignment allows to instantly map an object or array into many variables. - Destructuring assignment allows to instantly map an object or array into many variables.

View file

@ -0,0 +1,16 @@
That's because `arr.keys()` returns an iterable, but not an array.
We can convert it into an array using `Array.from`:
```js run
let arr = [];
arr[5] = true;
*!*
let numbers = Array.from(arr.keys());
*/!*
numbers.push(6);
alert(numbers); // 0,1,2,3,4,5,6
```

View file

@ -0,0 +1,28 @@
importance: 5
---
# Iterable keys
We want to get an array of numbers `0,1,2,3,4,5`.
The call to `arr.keys()` can be used, but there's a problem:
```js run
let arr = [];
arr[5] = true;
// loop works
for(let number of arr.keys()) {
alert(number); // 0,1,2,3,4,5
}
let numbers = arr.keys();
*!*
// Error: numbers.push is not a function
numbers.push(6);
*/!*
```
Why? How can we fix the code to let `numbers.push` work on the last one?

View file

@ -0,0 +1,143 @@
# Advanced loops
Now as we know arrays, iterables and destructuring, we're ready to understand cover few more forms of loops.
## Over objects
We've already seen one of the most popular loops: `for..in`
```js
for(let key in obj) {
// key iterates over object keys
}
```
But there are also ways to get keys, values or or key/value pairs as arrays:
- [Object.keys(obj)](mdn:js/Object/keys) -- returns the array of keys.
- [Object.values(obj)](mdn:js/Object/values) -- returns the array of values.
- [Object.entries(obj)](mdn:js/Object/entries) -- returns the array of `[key, value]` pairs.
For instance:
```js
let user = {
name: "John",
age: 30
};
```
- `Object.keys(user) = [name, age]`
- `Object.values(user) = ["John", 30]`
- `Object.entries(user) = [ ["name","John"], ["age",30] ]`
We can use `Object.values` to iterate over object values:
```js
for(let value of Object.values(obj)) {
// value iterates over object values
}
```
Here `Object.values(obj)` returns the array of properties, and `for..of` iterates over the array.
Also we can combine destructuring with `Object.entries` to iterate over key/value pairs:
```js
for(let [key, value] of Object.entries(obj)) {
// key,value iterate over properties
}
```
The example of all 3 loops:
```js run
let user = {
name: "John",
age: 30
};
// over keys
for(let key in user) {
alert(key); // name, then age
// can get the values with user[key]
}
// over values
for(let value of Object.values(user)) {
alert(value); // John, then 30
}
// over key/value pairs
for(let [key, value] of Object.entries(user)) {
alert(key + ':' + value); // name:John, then age:30
}
```
```smart header="The loops ignore symbolic properties"
All 3 forms of loops (and the given `Object` methods) ignore properties that use `Symbol(...)` as keys.
That's an expected behavior, because symbols are often created to make sure that the property can not be accessed accidentaly. There is a separate method named [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys (if we really know what we're doing). Also, the method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) returns all keys.
```
## Over arrays
There are many data structures that keep keys and/or values. `Object` is one of them, `Array` is the other one, and we'll see more in the future.
The method names `keys()`, `values()` and `entries()` are generic, there is a common agreement to use them for common iterations.
For arrays:
- [arr.keys()](mdn:js/Array/keys) -- returns the iterable for keys (indexes).
- [arr.entries()](mdn:js/Array/entries) -- returns the iterable for `[index, value]` pairs.
There is no `arr.values()`, because `for..of` iterates over values already.
Note that for arrays and further data structures the methods are callable directly, and they return iterables, not arrays.
The loops:
```js run
let users = ["John", "Pete", "Ann"];
// over keys (indexes)
for(let key of users.keys()) {
alert(key); // 0, 1, 2
}
// over values, basic for..of (no special method)
for(let value of users) {
alert(value); // John, then 30
}
// over key/value pairs
for(let [key, value] of users.entries()) {
alert(key + ':' + value); // 0:John, then 1:Pete, 2:Ann
}
```
````smart header="No holes"
All these methods treat arrays as contiguous. "Holes" are considered `undefined` items.
```js run
let arr = [];
arr[4] = "test";
*!*
// all keys till arr.length
*/!*
for(let i of arr.keys()) alert(i); // 0,1,2,3,4
alert(`Length: ${arr.length}`); // 5, remember, length is the last index + 1
```
````

View file

@ -0,0 +1 @@
# More syntax

View file

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 522 B

After

Width:  |  Height:  |  Size: 522 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 391 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 296 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 482 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 218 B

After

Width:  |  Height:  |  Size: 218 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 257 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 210 B

After

Width:  |  Height:  |  Size: 210 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 254 B

After

Width:  |  Height:  |  Size: 254 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 347 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 520 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 254 B

After

Width:  |  Height:  |  Size: 254 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 234 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more