work
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
@ -12,33 +12,19 @@ The main concept of memory management in Javascript is *reachability*.
|
||||||
|
|
||||||
Simply put, "reachable" values are those that are accessible now or in the future. They are guaranteed to be stored in memory.
|
Simply put, "reachable" values are those that are accessible now or in the future. They are guaranteed to be stored in memory.
|
||||||
|
|
||||||
1. There's a base set of reachable values. For instance:
|
1. There's a base set of inherently reachable values. For instance:
|
||||||
|
|
||||||
- Local variables and parameters of the current function.
|
- Local variables and parameters of the current function.
|
||||||
- Variables and parameters for other functions in the current chain of nested calls.
|
- Variables and parameters for other functions on the current chain of nested calls.
|
||||||
- Global variables.
|
- Global variables.
|
||||||
|
- (there are some other, internal ones as well)
|
||||||
|
|
||||||
These variables are called *roots*.
|
These values are called *roots*.
|
||||||
|
|
||||||
2. Any other value is retained in memory only while it's reachable from a root by a reference of by a chain of references.
|
2. Any other value is retained in memory only while it's reachable from a root by a reference of by a chain of references.
|
||||||
|
|
||||||
There's a background process that runs by the engine itself called [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). It monitors all objects and removes those that became unreachable.
|
There's a background process that runs by the engine itself called [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). It monitors all objects and removes those that became unreachable.
|
||||||
|
|
||||||
Note that only objects need reachability tracking, not primitives. For primitives things are very simple. They are copied as a whole on assignment, a single primitive can only be stored in one place, there can be no references for them. So if there was a string in a variable, and it was replaced with another string, then the old one can safely be junked.
|
|
||||||
|
|
||||||
Objects from the other hand can be referenced from multiple variables. If two variables store a reference to the same object, then even if one of them is overwritten, the object is still accessible through the second one. That's why a special "garbage collector" is needed that watches the references.
|
|
||||||
|
|
||||||
The basic garbage collection algorithm is called "mark-and-sweep".
|
|
||||||
|
|
||||||
Regularly the following "garbage collection" steps are performed:
|
|
||||||
|
|
||||||
- The garbage collector takes roots and follows all references from them.
|
|
||||||
- It remembers all objects it meets and follows their references.
|
|
||||||
- ...And so on until there are unvisited references.
|
|
||||||
- All objects except those are removed.
|
|
||||||
|
|
||||||
Let's see examples to get the better picture.
|
|
||||||
|
|
||||||
## A simple example
|
## A simple example
|
||||||
|
|
||||||
Here's the simplest example:
|
Here's the simplest example:
|
||||||
|
@ -88,7 +74,6 @@ user = null;
|
||||||
|
|
||||||
...Then the object is still reachable via `admin` global variable, so it's in memory. If we overwrite `admin` too, then it can be removed.
|
...Then the object is still reachable via `admin` global variable, so it's in memory. If we overwrite `admin` too, then it can be removed.
|
||||||
|
|
||||||
|
|
||||||
## Interlinked objects
|
## Interlinked objects
|
||||||
|
|
||||||
Now a more complex example. The family:
|
Now a more complex example. The family:
|
||||||
|
@ -130,7 +115,7 @@ But if we delete both, then we can see that John has no incoming references any
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**Outgoing references do not matter. Only incoming ones can make the object reachable.**
|
Outgoing references do not matter. Only incoming ones can make an object reachable.
|
||||||
|
|
||||||
John, the former `family.father` is now unreachable and will be removed from the memory with all its data that also became unaccessible.
|
John, the former `family.father` is now unreachable and will be removed from the memory with all its data that also became unaccessible.
|
||||||
|
|
||||||
|
@ -158,45 +143,62 @@ It is clearly seen that John and Ann are still linked, both have incoming refere
|
||||||
|
|
||||||
The former `"family"` object has been unlinked from the root, there's no reference to it any more, so the whole island became unreachable and will be removed.
|
The former `"family"` object has been unlinked from the root, there's no reference to it any more, so the whole island became unreachable and will be removed.
|
||||||
|
|
||||||
## Real-life algorithms
|
## Internal algorithms
|
||||||
|
|
||||||
```smart header="Advanced knowledge"
|
The basic garbage collection algorithm is called "mark-and-sweep".
|
||||||
The subsection contains advanced information about how things work internally. This knowledge is not required to continue learning Javascript.
|
|
||||||
```
|
|
||||||
|
|
||||||
The simple garbage collection algorithm that was described earlier in this chapter is not the best.
|
Regularly the following "garbage collection" steps are performed:
|
||||||
|
|
||||||
One of the main problems is that the collection process must be atomic: no new links should appear and no existing ones should be modified while it analyzes all references of existing objects and figures out which are unreachable.
|
- The garbage collector takes roots and "marks" them.
|
||||||
|
- Then it visits and "marks" all references from them.
|
||||||
|
- Then it visits marked objects and marks their references (the same object is not visited twice).
|
||||||
|
- ...And so on until there are unvisited references.
|
||||||
|
- All objects except marked ones are removed.
|
||||||
|
|
||||||
Essentially that means that the script execution must be paused to ensure that. Pauses may be small for simple scripts, but become noticeable (like 1000 ms or more) for big ones with many of objects. Such "hiccups" can be unpleasant and even disruptive for program systems that operate on real-time financial or medical data.
|
For instance, if our object structure might look like this:
|
||||||
|
|
||||||
So there are many optimizations to it.
|

|
||||||
|
|
||||||
In this section we use
|
Then the first step marks the roots:
|
||||||
|
|
||||||
### Generational collection
|

|
||||||
|
|
||||||
Most objects die very fast. For instance, local variables of functions usually don't exist for long. So a special memory area is created for them.
|
Then their references are marked:
|
||||||
|
|
||||||
This memory area is garbage-collected in a special way. Instead of
|

|
||||||
|
|
||||||
|
...And their refences, while possible:
|
||||||
|
|
||||||
So many optimizations and variations of the algorithm exist.
|

|
||||||
|
|
||||||
One of most widely used optimizations in JS engines is called "generational" garbage collection.
|
Now the objects that could not be visited in the process are considered unreachable and will be removed:
|
||||||
|
|
||||||
Objects are split into two sets: "old ones" and "new ones". Each set has its own memory area.
|

|
||||||
|
|
||||||
A new object is created in the "new" memory area and, if survived long enough, migrates to the "old" one. The "new" area is usually small and is garbage collected often. The "old" area is big and rarely cleaned up.
|
That's the concept how garbage collection works.
|
||||||
|
|
||||||
In practice that helps a lot, because many small objects are created and destroyed almost immediately. For instance when they are local variables of a function. And few objects survive for a long time, like the object with the current visitor data.
|
Javascript engines apply many optimizations to it, to make it run faster and be more hidden behind the scenes.
|
||||||
|
|
||||||
|
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".
|
||||||
|
- **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.
|
||||||
|
- **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.
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
The main things to know:
|
||||||
|
|
||||||
- Objects are retained in memory while they are reachable.
|
- Objects are retained in memory while they are reachable.
|
||||||
- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole.
|
- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole.
|
||||||
- Modern engines implement advanced algorithms of garbage collector that try to evade stop-the-world problem of the old ones.
|
|
||||||
|
|
||||||
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).
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
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: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
BIN
1-js/3-object-basics/2-garbage-collection/family.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
1-js/3-object-basics/2-garbage-collection/family@2x.png
Normal file
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 38 KiB |
BIN
1-js/3-object-basics/2-garbage-collection/garbage-collection.png
Normal file
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 5 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
@ -56,7 +56,7 @@ The "default" flavour occurs in [binary `+`](https://tc39.github.io/ecma262/#sec
|
||||||
|
|
||||||
So, to understand `ToNumber` and `ToString` for objects, we should redirect ourselves to `ToPrimitive`.
|
So, to understand `ToNumber` and `ToString` for objects, we should redirect ourselves to `ToPrimitive`.
|
||||||
|
|
||||||
## The new style: Symbol.toPrimitive
|
## The modern style: Symbol.toPrimitive
|
||||||
|
|
||||||
The internal `ToPrimitive(obj, hint)` call has two parameters:
|
The internal `ToPrimitive(obj, hint)` call has two parameters:
|
||||||
|
|
||||||
|
@ -90,29 +90,32 @@ alert(user + 1); // hint: default -> 31
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
For modern scripts, `Symbol.toPrimitive` can be enough. Two other methods: `toString` and `valueOf` exist for historical reasons and for backwards compatibility.
|
```smart header="`Symbol.toPrimitive` must return a primitive, but its type is not guaranteed"
|
||||||
|
The method `Symbol.toPrimitive` must return a primitive value, otherwise it will be an error.
|
||||||
|
|
||||||
|
But it can be any primitive. There's no control whether the call returns exactly a string or, say, a boolean.
|
||||||
|
|
||||||
<!--
|
If `ToPrimitive` is a part of a `ToNumber/ToString` conversion, then after it there will be one more step to transform the primitive to a string or a number.
|
||||||
The algorithm basically chooses which one of three methods to call:
|
```
|
||||||
|
|
||||||
1. First: try `obj[Symbol.toPrimitive](hint)` if exists.
|
## The old style: toString and valueOf
|
||||||
2. Otherwise:
|
|
||||||
1. For `hint == "string"` try to call `obj.toString()` and then `obj.valueOf()`.
|
|
||||||
2. for `hint == "number" or "default"` we try to call `obj.valueOf()` and then `obj.toString()`.
|
|
||||||
|
|
||||||
-->
|
If there is no `Symbol.toPrimitive`, then the other two methods are used:
|
||||||
|
|
||||||
|
- `toString` -- for string conversion,
|
||||||
|
- `valueOf` -- for numeric conversion.
|
||||||
|
|
||||||
### `toString` and `valueOf`
|
These methods are not symbols, because they come from ancient times when no symbols existed.
|
||||||
|
|
||||||
If there is no `Symbol.toPrimitive`, then for string conversions `toString` is tried and then `valueOf`, while for numeric or default conversions the order is `valueOf` -> `toString`.
|
The algorithm is:
|
||||||
|
|
||||||
|
1. If `hint == "string"` try to call `obj.toString()` and then `obj.valueOf()`.
|
||||||
|
2. If `hint == "number" or "default"` we try to call `obj.valueOf()` and then `obj.toString()`.
|
||||||
|
|
||||||
If the result of either method is not an object, then it is ignored.
|
If the result of either method is not an object, then it is ignored.
|
||||||
|
|
||||||
For instance, this `user` does the same as above:
|
For instance, this `user` does the same as above:
|
||||||
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let user = {
|
let user = {
|
||||||
name: "John",
|
name: "John",
|
||||||
|
@ -135,18 +138,26 @@ alert(+user); // 30
|
||||||
alert(user + 1); // 31 (default like number calls valueOf)
|
alert(user + 1); // 31 (default like number calls valueOf)
|
||||||
```
|
```
|
||||||
|
|
||||||
In most practical cases, only `toString` is implemented. Then it is used for both conversions.
|
If we only want a nice debugging output of our object, then we can implement `toString` only. It the absence of `valueOf` it will be used for both conversions.
|
||||||
|
|
||||||
```smart header="Methods must return a primitive, but its type is not guaranteed"
|
For instance:
|
||||||
If `toString/valueOf` return a non-primitive value, it is ignored. For `Symbol.toPrimitive`, it's even stricter: non-primitive is automatically an error. So the result of `ToPrimitive` algorithm as a whole can only be primitive.
|
|
||||||
|
|
||||||
But it can be any primitive. There's no control whether `toString()` returns exactly a string or, say, a boolean.
|
```js run
|
||||||
|
let user = {
|
||||||
|
name: "John",
|
||||||
|
|
||||||
If `ToPrimitive` is a part of a `ToNumber/ToString` transform, then after it there's one more step to transform the primitive to a string or a number.
|
toString() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// here the hint would be "number"
|
||||||
|
// only toString exists, so it is used
|
||||||
|
alert( user > 'Abba' ); // true, "John" > "Abba"
|
||||||
|
alert( user < 'Zeta' ); // true, "John" < "Zeta"
|
||||||
```
|
```
|
||||||
|
|
||||||
````smart header="ToBoolean?"
|
````smart header="There's no `ToBoolean`"
|
||||||
|
|
||||||
There is no such thing as `ToBoolean`. All objects (even empty) are `true` in boolean context:
|
There is no such thing as `ToBoolean`. All objects (even empty) are `true` in boolean context:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
|
@ -156,27 +167,11 @@ if ({}) alert("true"); // works
|
||||||
That is not customizable.
|
That is not customizable.
|
||||||
````
|
````
|
||||||
|
|
||||||
## Object: toString for the type
|
## 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.
|
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.
|
||||||
|
|
||||||
Here let's see the default `toString` and `valueOf` for plain objects.
|
But the built-in `toString()` of plain objects is also interesting, it does something very special.
|
||||||
|
|
||||||
### valueOf
|
|
||||||
|
|
||||||
By default, there is a built-in `valueOf()` for objects, but it returns the object itself:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let obj = { };
|
|
||||||
|
|
||||||
alert( obj === obj.valueOf() ); // true, valueOf returns the object itself
|
|
||||||
```
|
|
||||||
|
|
||||||
Because `ToPrimitive` algorithm ignores `valueOf` if it returns an object, we can assume that `valueOf` does not exist at all.
|
|
||||||
|
|
||||||
### toString
|
|
||||||
|
|
||||||
The `toString()` is much, much more interesting.
|
|
||||||
|
|
||||||
From the first sight it's obvious:
|
From the first sight it's obvious:
|
||||||
|
|
||||||
|
@ -188,38 +183,38 @@ alert( obj ); // [object Object]
|
||||||
|
|
||||||
But it's much more powerful than that.
|
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 type of an object instead of dots.
|
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 of the `toString()` for plain objects looks like this:
|
The algorithm looks like this:
|
||||||
|
|
||||||
- If `this` value is `undefined`, return `[object Undefined]`
|
- If `this` value is `undefined`, return `[object Undefined]`
|
||||||
- If `this` value is `null`, return `[object Null]`
|
- If `this` value is `null`, return `[object Null]`
|
||||||
- If `this` is a function, return `[object Function]`
|
- If `this` is a function, return `[object Function]`
|
||||||
- ...Then some other builtin cases...
|
- ...Some other builtin cases for strings, numbers etc...
|
||||||
- Otherwise return `[object @@toStringTag]`, where `@@toStringTag` is the value of `obj[Symbol.toStringTag]`.
|
- Otherwise if there's a property `obj[Symbol.toStringTag]`, then return it inside `[object...]`.
|
||||||
- Or, if no `toStringTag`, then return `[object 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, carry `Symbol.toStringTag` property.
|
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.
|
||||||
|
|
||||||
So this algorithm appears to be really universal.
|
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).
|
||||||
|
|
||||||
To make use of it, we should pass the thing to examine as `this`. We can do it using `func.call`:
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let s = {}.toString; // copy toString of a plain object to a variable
|
let s = {}.toString; // copy toString of a plain object into a variable
|
||||||
|
|
||||||
// null example
|
// call the algorithm with this = null
|
||||||
alert( s.call(null) ); // [object Null]
|
alert( s.call(null) ); // [object Null]
|
||||||
|
|
||||||
// function example
|
// call the algorithm with this = alert
|
||||||
alert( s.call(alert) ); // [object Function]
|
alert( s.call(alert) ); // [object Function]
|
||||||
|
|
||||||
// browser object example works too
|
// browser object works too
|
||||||
alert( s.call(window) ); // [object Window]
|
alert( s.call(window) ); // [object Window]
|
||||||
// (because it has Symbol.toStringTag)
|
// (because it has Symbol.toStringTag)
|
||||||
alert( window[Symbol.toStringTag] ); // Window
|
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`.
|
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:
|
We could also call it directly:
|
||||||
|
@ -227,6 +222,18 @@ We could also call it directly:
|
||||||
alert( {}.toString.call("test") ); // [object String]
|
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]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
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 |
|
@ -1,8 +1,12 @@
|
||||||
# Arrays
|
# Arrays
|
||||||
|
|
||||||
Arrays is the built-in subtype of objects, suited to store ordered collections.
|
Objects allow to store keyed collections of values. That's fine.
|
||||||
|
|
||||||
In this chapter we'll study them in-detail and learn the methods to manipulate them.
|
But quite often we find that we need an *ordered collection*, where we have a 1st, a 2nd, a 3rd element and so on. For example, we need that to store a list of something: users, goods, HTML elements etc.
|
||||||
|
|
||||||
|
It not convenient to use an object here, because it provides no methods to manage the order of elements. We can’t insert a new property “between” the existing ones. Objects are just not meant for such use.
|
||||||
|
|
||||||
|
There exists a special data structure named `Array`, to store ordered collections.
|
||||||
|
|
||||||
[cut]
|
[cut]
|
||||||
|
|
||||||
|
@ -15,7 +19,7 @@ let arr = new Array();
|
||||||
let arr = [];
|
let arr = [];
|
||||||
```
|
```
|
||||||
|
|
||||||
Almost all the time, the second syntax is used. We can supply the initial elements in the brackets:
|
Almost all the time, the second syntax is used. We can supply initial elements in the brackets:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let fruits = ["Apple", "Orange", "Plum"];
|
let fruits = ["Apple", "Orange", "Plum"];
|
||||||
|
@ -39,7 +43,7 @@ We can replace an element:
|
||||||
fruits[2] = 'Pear'; // now ["Apple", "Orange", "Pear"]
|
fruits[2] = 'Pear'; // now ["Apple", "Orange", "Pear"]
|
||||||
```
|
```
|
||||||
|
|
||||||
...Or add to the array:
|
...Or add a new one to the array:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
fruits[3] = 'Lemon'; // now ["Apple", "Orange", "Plum", "Lemon"]
|
fruits[3] = 'Lemon'; // now ["Apple", "Orange", "Plum", "Lemon"]
|
||||||
|
@ -78,7 +82,7 @@ arr[3](); // hello
|
||||||
|
|
||||||
|
|
||||||
````smart header="Trailing comma"
|
````smart header="Trailing comma"
|
||||||
An array may end with a comma:
|
An array, just like an object, may end with a comma:
|
||||||
```js
|
```js
|
||||||
let fruits = [
|
let fruits = [
|
||||||
"Apple",
|
"Apple",
|
||||||
|
@ -100,9 +104,11 @@ A [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) is one of mo
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
Arrays support both operations.
|
||||||
|
|
||||||
In practice we meet it very often. For example, a queue of messages that need to be shown on-screen.
|
In practice we meet it very often. For example, a queue of messages that need to be shown on-screen.
|
||||||
|
|
||||||
There's another closely related data structure named [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)).
|
There's another use case for arrays -- the data structure named [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)).
|
||||||
|
|
||||||
It supports two operations:
|
It supports two operations:
|
||||||
|
|
||||||
|
@ -115,11 +121,11 @@ A stack is usually illustrated as a pack of cards: new cards are added to the to
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Stacks are useful when we need to postpone a thing. Like, if we are busy doing something and get a new task to do, we can put (push) it onto the stack, and do so with all new tasks while we are busy. Later when we're done, we can take (pop) the latest task from the stack, and repeat it every time when we get freed. So, no task gets forgotten and the latest task is always the top to execute.
|
For stacks, the latest pushed item is received first, that's also called LIFO (Last-In-First-Out) principle. For queues, we have FIFO (First-In-First-Out).
|
||||||
|
|
||||||
Arrays in Javascript can work both as a queue and as a stack. They allow to add/remove elements both to/from the beginning or the end.
|
Arrays in Javascript can work both as a queue and as a stack. They allow to add/remove elements both to/from the beginning or the end.
|
||||||
|
|
||||||
In computer science such data structure is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue).
|
In computer science the data structure that allows it is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue).
|
||||||
|
|
||||||
**Methods that work with the end of the array:**
|
**Methods that work with the end of the array:**
|
||||||
|
|
||||||
|
@ -187,25 +193,25 @@ alert( fruits );
|
||||||
|
|
||||||
An array is a special kind of object. The square brackets used to access a property `arr[0]` actually come from the object syntax. Numbers are used as keys.
|
An array is a special kind of object. The square brackets used to access a property `arr[0]` actually come from the object syntax. Numbers are used as keys.
|
||||||
|
|
||||||
The "extras" are special methods to work with ordered collections of data and also the `length` property but at the core it's still an object.
|
They extend objects providing special methods to work with ordered collections of data and also the `length` property. But at the core it's still an object.
|
||||||
|
|
||||||
Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object.
|
Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object.
|
||||||
|
|
||||||
For instance, it is passed by reference, and modifications are visible from everywhere:
|
For instance, it is copied by reference:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let fruits = ["Banana"]
|
let fruits = ["Banana"]
|
||||||
|
|
||||||
let copy = fruits;
|
let arr = fruits; // copy by reference (two variables reference the same array)
|
||||||
|
|
||||||
alert( copy === fruits ); // the same object
|
alert( arr === fruits ); // true
|
||||||
|
|
||||||
copy.push("Pear"); // add to copy?
|
arr.push("Pear"); // modify the array by reference
|
||||||
|
|
||||||
alert( fruits ); // Banana, Pear - no, we modified fruits (the same object)
|
alert( fruits ); // Banana, Pear - 2 items now
|
||||||
```
|
```
|
||||||
|
|
||||||
But what really makes arrays special is their internal representation. The engine tries to store it's elements in the contiguous memory area, one after another, just as painted on the illustrations in this chapter. There are other optimizations as well, to make it work really fast.
|
...But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as painted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast.
|
||||||
|
|
||||||
But they all break if we quit working with an array as with an "ordered collection" and start working with it as if it were a regular object.
|
But they all break if we quit working with an array as with an "ordered collection" and start working with it as if it were a regular object.
|
||||||
|
|
||||||
|
@ -273,7 +279,7 @@ The similar thing with the `push` method.
|
||||||
|
|
||||||
## Loops
|
## Loops
|
||||||
|
|
||||||
We've already seen two suitable variants of `for` loop:
|
One of the oldest ways to cycle array items is the `for` loop over indexes:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let arr = ["Apple", "Orange", "Pear"];
|
let arr = ["Apple", "Orange", "Pear"];
|
||||||
|
@ -283,14 +289,21 @@ for (let i = 0; i < arr.length; i++) {
|
||||||
*/!*
|
*/!*
|
||||||
alert( arr[i] );
|
alert( arr[i] );
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
*!*
|
But for arrays there is another form of loop, `for..of`:
|
||||||
for(let item of arr) {
|
|
||||||
*/!*
|
```js run
|
||||||
alert( item );
|
let fruits = ["Apple", "Orange", "Plum"];
|
||||||
|
|
||||||
|
// iterates over array elements
|
||||||
|
for(let fruit of fruits) {
|
||||||
|
alert( fruit );
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `for..of` doesn't give access to the number of the current element, just its value, but in most cases that's enough. And it's shorter.
|
||||||
|
|
||||||
Technically, because arrays are objects, it is also possible to use `for..in`:
|
Technically, because arrays are objects, it is also possible to use `for..in`:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
|
@ -307,33 +320,16 @@ But that's actually a bad idea. There are potential problems with it:
|
||||||
|
|
||||||
1. The loop `for..in` iterates over *all properties*, not only the numeric ones.
|
1. The loop `for..in` iterates over *all properties*, not only the numeric ones.
|
||||||
|
|
||||||
There are so-called "array-like" objects in the browser and in other environments, that *look like arrays*. That is, they have `length` and indexes properties, and can be used in `for..of`, but they have *other non-numeric properties and methods*, which we usually don't need in the loop. The `for..in` will list them. If we need to work with array-like objects, then these "extra" properties can become a problem.
|
There are so-called "array-like" objects in the browser and in other environments, that *look like arrays*. That is, they have `length` and indexes properties, but they have *other non-numeric properties and methods*, which we usually don't need in the loop. The `for..in` will list them. If we need to work with array-like objects, then these "extra" properties can become a problem.
|
||||||
|
|
||||||
2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower.
|
2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup maybe important in few places bottlenecks or just irrelevant. But still we should be aware of the difference.
|
||||||
|
|
||||||
So we should never use `for..in` for arrays.
|
Generally, we shouldn't use `for..in` for arrays.
|
||||||
|
|
||||||
|
|
||||||
````smart header="`for..of` over `arr.entries()`"
|
|
||||||
For "real" arrays and some array-like structures, there's one more way:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let arr = ["Apple", "Orange", "Pear"];
|
|
||||||
|
|
||||||
*!*
|
|
||||||
for (let [i, item] of arr.entries()) {
|
|
||||||
*/!*
|
|
||||||
alert( i + ':' + item ); // 0:Apple, then 1:Orange, then 2:Pear
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here [arr.entries](mdn:js/Array/entries) is a built-in method, most array-like structures do not support that.
|
|
||||||
````
|
|
||||||
|
|
||||||
|
|
||||||
## A word about "length"
|
## A word about "length"
|
||||||
|
|
||||||
The `length` property automatically updates when we modify the array. It is actually not the *count* of values in the array, but the greatest numeric index plus one.
|
The `length` property automatically updates when we modify the array. To be precies, it is actually not the count of values in the array, but the greatest numeric index plus one.
|
||||||
|
|
||||||
For instance, a single element with a large index gives a big length:
|
For instance, a single element with a large index gives a big length:
|
||||||
|
|
||||||
|
@ -344,9 +340,9 @@ fruits[123] = "Apple";
|
||||||
alert( fruits.length ); // 124
|
alert( fruits.length ); // 124
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that we usually don't use arrays like that. Want to stick a value in the middle of nowhere? Use an object, unless really know what you're doing.
|
Note that we usually don't use arrays like that.
|
||||||
|
|
||||||
Another interesting thing about the `length` property is that it's actually writable.
|
Another interesting thing about the `length` property is that it's writable.
|
||||||
|
|
||||||
If we increase it manually, nothing interesting happens. But if we decrease it, the array is truncated. The process is irreversable, here's the example:
|
If we increase it manually, nothing interesting happens. But if we decrease it, the array is truncated. The process is irreversable, here's the example:
|
||||||
|
|
||||||
|
@ -403,6 +399,38 @@ let matrix = [
|
||||||
alert( matrix[1][1] ); // the central element
|
alert( matrix[1][1] ); // the central element
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## toString
|
||||||
|
|
||||||
|
Arrays have their own implementation of `toString` method that returns a comma-separated list of elements.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let arr = [1, 2, 3];
|
||||||
|
|
||||||
|
alert( arr ); // 1,2,3
|
||||||
|
alert( String(arr) === '1,2,3' ); // true
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, let's try this:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
alert( [] + 1 ); // "1"
|
||||||
|
alert( [1] + 1 ); // "11"
|
||||||
|
alert( [1,2] + 1 ); // "1,21"
|
||||||
|
```
|
||||||
|
|
||||||
|
Arrays do not have `Symbol.toPrimitive`, neither a viable `valueOf`, they implement only `toString` conversion, so here `[]` becomes an empty string, `[1]` becomes `"1"` and `[1,2]` becomes `"1,2".
|
||||||
|
|
||||||
|
When the binary plus `"+"` operator adds something to a string, it converts it to a string as well, so the next step looks like this:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
alert( "" + 1 ); // "1"
|
||||||
|
alert( "1" + 1 ); // "11"
|
||||||
|
alert( "1,2" + 1 ); // "1,21"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
@ -433,7 +461,7 @@ We can use an array as a deque with the following operations:
|
||||||
To loop over the elements of the array:
|
To loop over the elements of the array:
|
||||||
- `for(let i=0; i<arr.length; i++)` -- works fastest, old-browser-compatible.
|
- `for(let i=0; i<arr.length; i++)` -- works fastest, old-browser-compatible.
|
||||||
- `for(let item of arr)` -- the modern syntax for items only,
|
- `for(let item of arr)` -- the modern syntax for items only,
|
||||||
- `for(let [i,item] of arr.entries())` -- the modern syntax for indexes together with items,
|
|
||||||
- `for(let i in arr)` -- never use.
|
- `for(let i in arr)` -- never use.
|
||||||
|
|
||||||
That were the "extended basics". There are more methods. In the next chapter we'll study them in detail.
|
|
||||||
|
That were the "extended basics". There are more methods, and in the future chapter <mdn:array-methods> we will return to them.
|
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 |
|
@ -1,11 +1,11 @@
|
||||||
|
|
||||||
# Iterables
|
# Iterables
|
||||||
|
|
||||||
*Iterable* objects is a general concept that allows to make any object to be useable in a `for..of` loop.
|
*Iterable* objects is a general concept that allows to make any object 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.
|
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.
|
Iterables come from the very core of Javascript and are widely used both in built-in methods and those provided by the environment.
|
||||||
|
|
||||||
[cut]
|
[cut]
|
||||||
|
|
||||||
|
@ -27,14 +27,14 @@ let range = {
|
||||||
// for(let num of range) ... num=1,2,3,4,5
|
// 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).
|
To make the `range` iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that).
|
||||||
|
|
||||||
- When `for..of` starts, it calls that method (or errors if none found).
|
- 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`.
|
- 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.
|
- 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.
|
- 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`:
|
Here's the full impelementation for `range`:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let range = {
|
let range = {
|
||||||
|
@ -42,17 +42,18 @@ let range = {
|
||||||
to: 5
|
to: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
// call to for..of initially calls this
|
// 1. call to for..of initially calls this
|
||||||
range[Symbol.iterator] = function() {
|
range[Symbol.iterator] = function() {
|
||||||
|
|
||||||
// ...it returns the iterator:
|
// 2. ...it returns the iterator:
|
||||||
return {
|
return {
|
||||||
current: this.from,
|
current: this.from, // remember "from" and "to" of the range
|
||||||
last: this.to,
|
last: this.to, // in object properties
|
||||||
|
|
||||||
// ...whose next() is called on each iteration of the loop
|
// 3. next() is called on each iteration of the loop
|
||||||
next() {
|
next() {
|
||||||
if (this.current <= this.last) {
|
if (this.current <= this.last) {
|
||||||
|
// 4. iterator returns the value as an object {done:.., value :...}
|
||||||
return { done: false, value: this.current++ };
|
return { done: false, value: this.current++ };
|
||||||
} else {
|
} else {
|
||||||
return { done: true };
|
return { done: true };
|
||||||
|
@ -64,8 +65,6 @@ range[Symbol.iterator] = function() {
|
||||||
for (let num of range) {
|
for (let num of range) {
|
||||||
alert(num); // 1, then 2, 3, 4, 5
|
alert(num); // 1, then 2, 3, 4, 5
|
||||||
}
|
}
|
||||||
|
|
||||||
alert(Math.max(...range)); // 5 (*)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
There is an important separation of concerns in this code.
|
There is an important separation of concerns in this code.
|
||||||
|
@ -78,12 +77,6 @@ The fact that the object itself does not do the iteration also adds flexibility,
|
||||||
|
|
||||||
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.
|
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"
|
```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.
|
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.
|
||||||
|
|
||||||
|
@ -108,6 +101,16 @@ let range = {
|
||||||
|
|
||||||
## Built-in iterables
|
## Built-in iterables
|
||||||
|
|
||||||
|
Arrays and strings are most widely used built-in iterables.
|
||||||
|
|
||||||
|
For a string, `for..of` loops over its characters:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
for(let char of "test") {
|
||||||
|
alert( char ); t, then e, then s, then t
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Iterators can also be created explicitly, without `for..of`, with a direct call of `Symbol.iterator`. For built-in objects too.
|
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":
|
For instance, this code gets a string iterator and calls it "manually":
|
||||||
|
@ -140,7 +143,7 @@ Sometimes they can both be applied. For instance, strings are both iterable and
|
||||||
|
|
||||||
But an iterable may be not array-like and vise versa.
|
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`.
|
For example, the `range` in the example above is iterable, but not array-like, because it does not have indexed properties and `length`.
|
||||||
|
|
||||||
And here's the object that is array-like, but not iterable:
|
And here's the object that is array-like, but not iterable:
|
||||||
|
|
||||||
|
@ -152,41 +155,14 @@ let arrayLike = { // has indexes and length => array-like
|
||||||
};
|
};
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
// Error (not iterable)
|
// Error (not Symbol.iterator)
|
||||||
for(let item of arrayLike) {}
|
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.
|
But what they share in common -- both iterables and array-likes are usually *not arrays*, they don't have `push`, `pop` etc. That's rather inconvenient if we received such object and want to work with it as with an array.
|
||||||
|
|
||||||
Remember the special `arguments` object for a function call? It is both array-like and iterable object that has all function arguments:
|
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. Then we can call array methods on it.
|
||||||
|
|
||||||
```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:
|
For instance:
|
||||||
|
|
||||||
|
@ -197,17 +173,16 @@ let arrayLike = {
|
||||||
length: 2
|
length: 2
|
||||||
};
|
};
|
||||||
|
|
||||||
alert( Array.from(arrayLike).join() ); // Hello,World
|
let arr = Array.from(arrayLike);
|
||||||
|
alert(arr.pop()); // World (method works)
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// assuming range is taken from the example above
|
// assuming that range is taken from the example above
|
||||||
alert( Array.from(range) ); // 1,2,3,4,5
|
let arr = Array.from(range);
|
||||||
|
alert(arr); // 1,2,3,4,5 (toString of Array gives a list of items)
|
||||||
```
|
```
|
||||||
|
|
||||||
This method is really handy when we want to use array methods like `map`, `forEach` and others on array-likes or iterables.
|
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Objects that can be used in `for..of` are called *iterable*.
|
Objects that can be used in `for..of` are called *iterable*.
|
||||||
|
@ -218,7 +193,7 @@ Objects that can be used in `for..of` are called *iterable*.
|
||||||
- The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly.
|
- 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`.
|
- 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.
|
The modern specification mostly uses iterables instead of arrays where an ordered collection is required, because they are more abstract. We'll see more examples of it very soon.
|
||||||
|
|
||||||
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.
|
||||||
|
|
1
1-js/3-object-basics/index.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Object basics
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |