【注】其实吸引我的是这张配图。。。让我想起了卷福划的圈,有没有~~
我们将在文章中分析一下Iterators(迭代器)。Iterators是JS中循环集合的一种新的方式。它是ES6中引入的,已经在很多场景中被广泛使用,因此已经变得非常受欢迎。
简介
如果你有这样一个数组:
const myFavouriteAuthors = [
'Neal Stephenson',
'Arthur Clarke',
'Isaac Asimov',
'Robert Heinlein'
];
有时候,你想要获取数组里的每个值,用来打印,操作,或者做一些其它逻辑。如果我问你怎么做?你肯定会说 —— 这很简单。“用for
,while
,for-of
就行了”。实现将会是这样:
设想一下,如果你需要自定义一下刚刚那个数组结构,比如保存作者信息,像这样:
现在,myFavouriteAuthors
是一个对象了,包含了另一个对象allAuthors
。allAuthors
又包含了3个数组,fiction
、scienceFiction
以及fantasy
。现在,如果我再问你如何通过循环myFavouriteAuthors
来获取所有作者,那你会怎么来实现呢?你可以继续尝试用复合循环来获取所有数据。
不过,如果你这样做:
for (let author of myFavouriteAuthors) {
console.log(author)
}
// TypeError: {} is not iterable
你将会得到TypeError
说object不能被迭代。让我们看下可迭代是什么意思,并且我们如何才能让这个object可以迭代。在文章最后,你将会知道如何用for-of
去迭代这个myFavouriteAuthors
。
**Iterables(迭代) and Iterators(迭代器)
上一节我们遇到了个问题。在我们自定义的对象里获取所有作者信息不是那么的方便。我们需要某种方法,能够把所有(对象)内数据有顺序的暴露出来。
让我们在myFavouriteAuthors
里加一个方法getAllAuthors
,用来返回所有作者,像这样:
这是个简单的实现。它完成了我们的任务。然而,这样会出现一些问题:
getAllAuthors
这个命名太自由了。如果有些人有一个他们自己的myFavouriteAuthors
,他们可能会取名叫retrieveAllAuthors
。- 作为一名开发人员,我们就总要去找这个用来获取所有数据的方法名字。当然在这里,它叫
getAllAuthors
。 getAllAuthors
返回的是包含所有作者字符串的数组。但如果另外一个开发人员返回了一个对象组成的数组,怎么办呢?
[ {name: 'Agatha Christie'}, {name: 'J. K. Rowling'}, ... ]
那么开发人员必须要知道这个方法的准确名字以及返回的数据格式。
如果我们定一个规则,方法的名字和返回类型是固定不可变的呢?
让我们把这个方法叫做 —— iteratorMethod
ECMA采取了类似的步骤来标准化这个循环自定义对象的过程。然而,ECMA用名为Symbol.iterator
来代替了iteratorMethod
名字。Symbols提供了一个独一无二的名字(唯一,并且不会和别的属性名发生冲突)。Symbol.iterator
将会返回一个叫做iterator
的对象。这个iterator有一个next
的方法,这个方法会返回一个用value
和done
作为key的对象。
这个叫value
的key将包含当前值。它是任何类型的。这个done
是一个布尔值。它表示是否已经获取到了所有的值。
用一张图来帮助说明一下 iterables, iterators , next 之间的关系。而 这个关系被称为 Iteration Protocol(迭代协议) 。
按照Axel Rauschmayer的Exploring JS这本书所说:
- 迭代是一种数据结构,能公开它的元素可访问性。这个
Symbol.iterator
就是用来做这件事的方法。这个方法是一个 迭代器 工厂,也就是说,它将会创建一个 迭代器 。 - iterator(迭代器) 是指向用来遍历元素数据集的指针。
** 让 objects
变得可迭代
我们已经从上一节了解到,我们需要实现一个叫做 Symbol.iterator
的方法。我们将会使用computed property syntax(可计算的属性语法)来设置这个key。示例如下:
第四行,我们定义了一个迭代器。是一个带有next
方法的对象。这个next
方法返回的值取决于step
变量。在25行,我们获取了这个iterator
。27行,我们调用next
。我们调用next多次,直到done
变成true
。
这正是在for-of
循环里发生的事情。for-of
拿到这个对象的迭代器,然后循环调用next()
直到done
变成true。
JavaScript中的迭代
JS里有很多可迭代的东西。可能不是显而易见的,但是你仔细检查一下,迭代器就会出来了。
这些是可迭代的:
- Arrays and TypedArrays
- Strings —— 能迭代出每个字符或者Unicode码
- Maps —— 迭代出key-value对
- Sets —— 迭代出元素
arguments
—— 类似array- DOM元素 —— 正在开发中。。。
这些是会使用迭代器的:
for-of
循环 —— 需要对象可迭代,否则会抛出一个TypeError
。
for (const value of iterable) { ... }
- 数组的解构 —— 因为可迭代而发生了解构。让我们看下:
const array = ['a', 'b', 'c', 'd', 'e'];
const [first, ,third, ,last] = array;
这样就等同于:
const array = ['a', 'b', 'c', 'd', 'e'];
const iterator = array[Symbol.iterator]();
const first = iterator.next().value
iterator.next().value // Since it was skipped, so it's not assigned
const third = iterator.next().value
iterator.next().value // Since it was skipped, so it's not assigned
const last = iterator.next().value
- ...操作符
代码如下:
const array = ['a', 'b', 'c', 'd', 'e'];
const newArray = [1, ...array, 2, 3];
等同于:
const array = ['a', 'b', 'c', 'd', 'e'];
const iterator = array[Symbol.iterator]();
const newArray = [1];
for (let nextValue = iterator.next(); nextValue.done !== true; nextValue = iterator.next()) {
newArray.push(nextValue.value);
}
newArray.push(2)
newArray.push(3)
- Maps and Sets
Map的构造函数会迭代出[key,value]
放进Map里,Set的构造函数会迭代出元素放进Set里:
const map = new Map([[1, 'one'], [2, 'two']]);
map.get(1)
// one
const set = new Set(['a', 'b', 'c]);
set.has('c');
// true
- 迭代器也是理解generator函数的前提
让myFavouriteAuthors
可以迭代
直接上代码:
const myFavouriteAuthors = {
allAuthors: {
fiction: [
'Agatha Christie',
'J. K. Rowling',
'Dr. Seuss'
],
scienceFiction: [
'Neal Stephenson',
'Arthur Clarke',
'Isaac Asimov',
'Robert Heinlein'
],
fantasy: [
'J. R. R. Tolkien',
'J. K. Rowling',
'Terry Pratchett'
],
},
[Symbol.iterator]() {
// Get all the genre in an array
const genres = Object.values(this.allAuthors);
// Store the current genre and author index
let currentAuthorIndex = 0;
let currentGenreIndex = 0;
return {
// Implementation of next()
next() {
// authors according to current genre index
const authors = genres[currentGenreIndex];
// doNotHaveMoreAuthors is true when the authors array is exhausted.
// That is, all items are consumed.
const doNothaveMoreAuthors = !(currentAuthorIndex < authors.length);
if (doNothaveMoreAuthors) {
// When that happens, we move the genre index to the next genre
currentGenreIndex++;
// and reset the author index to 0 again to get new set of authors
currentAuthorIndex = 0;
}
// if all genres are over, then we need tell the iterator that we
// can not give more values.
const doNotHaveMoreGenres = !(currentGenreIndex < genres.length);
if (doNotHaveMoreGenres) {
// Hence, we return done as true.
return {
value: undefined,
done: true
};
}
// if everything is correct, return the author from the
// current genre and incerement the currentAuthorindex
// so next time, the next author can be returned.
return {
value: genres[currentGenreIndex][currentAuthorIndex++],
done: false
}
}
};
引用:
-
ExploringJS by Dr Axel Rauschmayer.