This commit is contained in:
Ilya Kantor 2017-07-01 10:29:07 +03:00
parent d391eb1f3c
commit db182040c8
2 changed files with 19 additions and 18 deletions

View file

@ -5,7 +5,7 @@
Arrays by themselves are iterable. But not only arrays. Strings are iterable too, and many other built-in objects as well. Arrays by themselves are iterable. But not only arrays. Strings are iterable too, and many other built-in objects as well.
Iterables are widely used by the core JavaScript, as we'll see many operators and built-in methods rely on them. Iterables are widely used by the core JavaScript. As we'll see many built-in operators and methods rely on them.
[cut] [cut]
@ -23,13 +23,13 @@ let range = {
to: 5 to: 5
}; };
// We want for..of to work: // We want the for..of to work:
// 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 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). 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 not 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}`, where `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}`, where `done=true` means that the iteration is finished, otherwise `value` must be the new value.
@ -47,13 +47,13 @@ range[Symbol.iterator] = function() {
// 2. ...it returns the iterator: // 2. ...it returns the iterator:
return { return {
current: this.from, // start at "range.from", current: this.from,
last: this.to, // end at "range.to" last: this.to,
// 3. next() is called on each iteration by for..of // 3. next() is called on each iteration by the for..of loop
next() { next() {
if (this.current <= this.last) {
// 4. it should return the value as an object {done:.., value :...} // 4. it should return the value as an object {done:.., value :...}
if (this.current <= this.last) {
return { done: false, value: this.current++ }; return { done: false, value: this.current++ };
} else { } else {
return { done: true }; return { done: true };
@ -62,6 +62,7 @@ range[Symbol.iterator] = function() {
}; };
}; };
// now it works!
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
} }
@ -72,9 +73,9 @@ There is an important separation of concerns in this code:
- The `range` itself does not have the `next()` method. - 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]()`, and it handles the iteration. - Instead, another object, a so-called "iterator" is created by the call to `range[Symbol.iterator]()`, and it handles the iteration.
So, the iterator is separate from the object. So, the iterator object is separate from the object it iterates over.
Technically, we may merge them and use `range` itself as the iterator, to make the code simpler. Technically, we may merge them and use `range` itself as the iterator to make the code simpler.
Like this: Like this:
@ -102,7 +103,7 @@ for (let num of range) {
} }
``` ```
Now `range[Symbol.iterator]()` returns the `range` object itself, and it has the necessary `next()` method. Sometimes that's fine too. The downside is that now it's impossible to have two `for..of` loops running over the object simultaneously: they'll share the iteration state, because there's only one iterator -- the object itself. Now `range[Symbol.iterator]()` returns the `range` object itself: it has the necessary `next()` method and remembers the current iteration progress in `this.current`. Sometimes that's fine too. The downside is that now it's impossible to have two `for..of` loops running over the object simultaneously: they'll share the iteration state, because there's only one iterator -- the object itself.
```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.
@ -138,9 +139,9 @@ for(let char of str) {
Normally, internals of iterables are hidden from the external code. There's a `for..of` loop, that works, that's all it needs to know. Normally, internals of iterables are hidden from the external code. There's a `for..of` loop, that works, that's all it needs to know.
But to understand things a little bit more deeper let's see how to create an iterator explicitly. We'll do that the same way as `for..of`, but with direct calls. But to understand things a little bit more deeper let's see how to create an iterator explicitly.
For instance, this code gets a string iterator and calls it "manually": We'll iterate over a string the same way as `for..of`, but with direct calls. This code gets a string iterator and calls it "manually":
```js run ```js run
let str = "Hello"; let str = "Hello";
@ -157,16 +158,16 @@ while(true) {
} }
``` ```
That is rarely needed, but gives us more control than `for..of`. For instance, we can split the iteration process: iterate a bit, then stop, do something else, and then resume later. That is rarely needed, but gives us more control over the process than `for..of`. For instance, we can split the iteration process: iterate a bit, then stop, do something else, and then resume later.
## Iterables and array-likes [#array-like] ## Iterables and array-likes [#array-like]
There are two official terms that look similar, but are very different. Please be careful to avoid the confusion. There are two official terms that look similar, but are very different. Please make sure you understand them well to avoid the confusion.
- *Iterables* are objects that implement the `Symbol.iterator` method, as described above. - *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. - *Array-likes* are objects that have indexes and `length`, so they look like arrays.
Naturally, they can combine. For instance, strings are both iterable and array-like. Naturally, these properties can combine. For instance, strings are both iterable (`for..of` works on them) and array-like (they have numeric indexes and `length`).
But an iterable may be not array-like and vice versa. But an iterable may be not array-like and vice versa.
@ -236,7 +237,7 @@ let arr = Array.from(range, num => num * num);
alert(arr); // 1,4,9,16,25 alert(arr); // 1,4,9,16,25
``` ```
We can also use `Array.from` to turn a string into an array of characters: Here we use `Array.from` to turn a string into an array of characters:
```js run ```js run
let str = '𝒳😂'; let str = '𝒳😂';

View file

@ -334,9 +334,9 @@ Here's what's going on in the `makeCounter` example step-by-step, follow it to m
3. During the execution of `makeCounter()`, a tiny nested function is created. 3. During the execution of `makeCounter()`, a tiny nested function is created.
It doesn't matter whether the function is created using Function Declaration or Function Expression. All functions get the `[[Environment]]` property that references the Lexical Environment where they were made. It doesn't matter whether the function is created using Function Declaration or Function Expression. All functions get the `[[Environment]]` property that references the Lexical Environment where they were made. So that new tiny nested function gets it as well.
For our new nested function that is the current Lexical Environment of `makeCounter()`: For our new nested function the value of `[[Environment]]` is the current Lexical Environment of `makeCounter()` (where it was born):
![](lexenv-nested-makecounter-3.png) ![](lexenv-nested-makecounter-3.png)