minor
This commit is contained in:
parent
d391eb1f3c
commit
db182040c8
2 changed files with 19 additions and 18 deletions
|
@ -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() {
|
||||||
|
// 4. it should return the value as an object {done:.., value :...}
|
||||||
if (this.current <= this.last) {
|
if (this.current <= this.last) {
|
||||||
// 4. it should return 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 };
|
||||||
|
@ -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 = '𝒳😂';
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue