Итераторы и цикл for...of
1. Итераторы
1. Концепция
Итератор является таким механизмом. Это интерфейс, обеспечивающий унифицированный механизм доступа к различным структурам данных. Пока любая структура данных развертывает интерфейс Iterator, она может выполнять операцию обхода (то есть обрабатывать по очереди все элементы структуры данных).
Итератор имеет три функции:
- Один из них — предоставить унифицированный и удобный интерфейс доступа к различным структурам данных;
- Второй — позволить элементам структуры данных располагаться в определенном порядке;
- В-третьих, в ES6 создана новая команда обхода for…of loop, а интерфейс Iterator в основном используется для for…of потребления.
2. Принцип работы
-
Создайте объект-указатель, указывающий на начало текущей структуры данных. Другими словами, объект обхода по существу является объектом указателя.
-
При первом
next
вызове метода объекта указателя указатель может указывать на первый член структуры данных. -
При втором вызове метода объекта указателя
next
указатель указывает на второй член структуры данных. -
Продолжайте вызывать методы объекта указателя,
next
пока он не укажет на конец структуры данных.
При каждом вызове метода next
возвращается объект, содержащий два свойства value
и . done
Среди них value
атрибут представляет собой значение текущего члена, а done
атрибут представляет собой логическое значение, указывающее, завершен ли обход.
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{
value: array[nextIndex++], done: false} :
{
value: undefined, done: true};
}
};
}
3. Интерфейс Iterator по умолчанию
Пока структура данных развертывает интерфейс Iterator, мы называем эту структуру данных «проходимой» (итерируемой).
ES6 предусматривает, что интерфейс Iterator по умолчанию развертывается в Symbol.iterator
свойствах структуры данных, или, другими словами, пока структура данных имеет Symbol.iterator
свойства, ее можно считать «итерируемой».
Symbol.iterator
Сам атрибут является функцией, которая по умолчанию является функцией генерации обходчика текущей структуры данных. Выполнение этой функции возвращает итератор.
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
ES6 создает новый командный for…of
цикл обхода, интерфейс Iterator в основном предназначен для for…of
использования.
Собственные данные с интерфейсом итератора (можно for...of
пройти)
- Множество
- карта
- Набор
- Нить
- типизированный массив
- объект аргументов функции
- Объект NodeList
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
4. При вызове интерфейса Iterator
В некоторых случаях интерфейс Iterator (то есть метод) будет вызываться по умолчанию.Помимо цикла, Symbol.iterator
описанного ниже , есть несколько других случаев.for...of
(1) Деструктивное присвоение
Метод Symbol.iterator будет вызываться по умолчанию при деструктурировании и назначении массивов и структур Set.
let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
// x='a'; y='b'
let [first, ...rest] = set;
// first='a'; rest=['b','c'];
(2) Оператор спреда
Оператор распространения (…) также вызывает интерфейс Iterator по умолчанию.
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
(3) выход*
yield*
Вслед за проходимой структурой он вызывает интерфейс обхода структуры.
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
(4) Другие случаи
Поскольку обход массива вызовет интерфейс обходчика, любой случай, который принимает массив в качестве параметра, фактически вызывает интерфейс обходчика. Ниже приведены некоторые примеры.
- для… из
- Массив.от()
- Map(), Set(), WeakMap(), WeakSet() (например
new Map([['a',1],['b',2]])
) - Обещание.все()
- Обещание.гонка()
2. for… цикла
1. Массив
const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v); // red green blue
}
for...of
Вместо методов экземпляров массива можно использовать циклы forEach
.
const arr = ['red', 'green', 'blue'];
arr.forEach(function (element, index) {
console.log(element); // red green blue
console.log(index); // 0 1 2
});
Исходный цикл JavaScript for...in
может получить только имя ключа объекта и не может напрямую получить значение ключа. ES6 предоставляет for...of
циклы, позволяющие обход для получения ключевых значений.
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}
for...of
Цикл вызывает интерфейс итератора, а интерфейс итератора для массивов возвращает только свойства с числовыми индексами. Это for...in
не то же самое, что цикл.
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
В приведенном выше коде for...of
цикл не возвращает свойство foo массива arr.
2. Структура набора и карты
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e);
}
// Gecko
// Trident
// Webkit
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262
3. Массивоподобные объекты
Массивоподобные объекты включают в себя несколько классов. Ниже приведены примеры циклов for...of для строк, объектов DOM NodeList и объектов arguments.
// 字符串
let str = "hello";
for (let s of str) {
console.log(s); // h e l l o
}
// DOM NodeList对象
let paras = document.querySelectorAll("p");
for (let p of paras) {
p.classList.add("test");
}
// arguments对象
function printArgs() {
for (let x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
// 'a'
// 'b'
Не все массивоподобные объекты имеют интерфейс Iterator, простым решением является использование метода Array.from для преобразования его в массив.
let arrayLike = {
length: 2, 0: 'a', 1: 'b' };
// 报错
for (let x of arrayLike) {
console.log(x);
}
// 正确
for (let x of Array.from(arrayLike)) {
console.log(x);
}
4. Объект
Для обычных объектов for...of
структуру нельзя использовать напрямую, и будет сообщено об ошибке, а интерфейс Iterator должен быть развернут, прежде чем его можно будет использовать. Однако в этом случае for...in
цикл все еще можно использовать для обхода имен ключей.
let es6 = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
};
for (let e in es6) {
console.log(e);
}
// edition
// committee
// standard
for (let e of es6) {
console.log(e);
}
// TypeError: es6[Symbol.iterator] is not a function
Приведенный выше код указывает, что для обычных объектов for...in
цикл может пройти по имени ключа, и for...of
цикл сообщит об ошибке.
Одним из решений является использование Object.keys
метода для создания массива имен ключей объекта, а затем обход массива.
for (var key of Object.keys(someObject)) {
console.log(key + ': ' + someObject[key]);
}
Другой способ — использовать функцию Generator для переупаковки объекта.
const obj = {
a: 1, b: 2, c: 3 }
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(obj)) {
console.log(key, '->', value);
}
// a -> 1
// b -> 2
// c -> 3
3. Сравнение с другими синтаксисами обхода
Взяв в качестве примера массивы, JavaScript предоставляет разнообразный синтаксис обхода. Самый примитивный способ записи — это цикл for.
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
Этот способ записи громоздок, поэтому массивы предоставляют встроенные forEach
методы.
myArray.forEach(function (value) {
console.log(value);
});
Проблема с таким способом написания заключается в том, что нет возможности выйти из forEach
цикла, и ни break
команда, ни return
команда не будут работать.
for...in
Цикл может перебирать ключи массива.
for (var index in myArray) {
console.log(myArray[index]);
}
for...in
Циклы имеют несколько недостатков.
- Ключи массива — числа, но
for...in
циклнитьВ качестве имен ключей «0», «1», «2» и так далее. for...in
Цикл перебирает не только числовые имена ключей, но и другие ключи, добавленные вручную, даже ключи в цепочке прототипов.- В некоторых случаях цикл for...in перебирает ключи в произвольном порядке.
Короче говоря, for...in
цикл в основном дляпересекать объектыПо дизайну он не подходит для обхода массивов.
for...of
По сравнению с несколькими описанными выше подходами зацикливание имеет ряд существенных преимуществ.
for (let value of myArray) {
console.log(value);
}
- Имеет
for...in
такой же лаконичный синтаксис, но безfor...in
тех недостатков. - В отличие от
forEach
методов, его можноbreak、continue和return
использовать с . - Предоставляет унифицированный рабочий интерфейс для обхода всех структур данных.
Ниже приведен пример использования break
оператора для выхода из for...of
цикла.
for (var n of fibonacci) {
if (n > 1000)
break;
console.log(n);
}
В приведенном выше примере будут выведены элементы, последовательность Фибоначчи которых меньше или равна 1000. Если текущий элемент больше 1000, break
оператор будет использован для выхода for...of
из цикла.