Итерируемые объекты в JavaScript

Протокол итераций позволяет объектам JavaScript определять и настраивать свое поведение при итерации, то есть указать, какие значения должны быть проитерированы в for...of.

Массивы считаются итерируемыми. Но есть и другие итерируемые объекты (например, строки).

Описание Symbol.iterator

Давайте попробуем создать итерируемый объект.

Например, есть объект, который не является массивом, но подходит для использования в for...of.

Посмотрим на пример объекта диапазона, представляющего интервал чисел:

let range = {
  from: 0,
  to: 10
}; 
// We want the for..of to work:
// for(let num of range) ... num=0,1,2,3,4,5,6,7,8,9,10

Чтобы преобразовать диапазон в итерируемый объект, необходимо добавить к объекту метод под названием Symbol.iterator.

Это происходит следующим образом:

  1. Как только начинается for..of, метод вызывается один раз. Он должен вернуть итератор.
  2. Затем, for..of может работать с возвращенным объектом.
  3. Когда for..of ожидает следующее значение, на этом объекте вызывается метод next().
  4. Его результат должен включать форму {done: Boolean, value: any} .

Весь процесс для диапазона с замечаниями будет выглядеть так:

Javascript range object call Symbol.iterator
let range = { from: 0, to: 5 }; // 1. called to for..of initially calls this range[Symbol.iterator] = function () { //returns an iterator object: // 2.for..of works only with this iterator, querying it for next values return { currentValue: this.from, lastValue: this.to, // 3. next() is called at each iteration with a for..of loop next() { // 4. it should return the value as an object {done:.., value :...} if (this.currentValue <= this.lastValue) { return { done: false, value: this.currentValue++ }; } else { return { done: true }; } } }; }; for (let num of range) { console.log(num); // 0, then 1, 2, 3, 4, 5 }

Самой важной особенностью итерируемых объектов является разделение ответственности. Оно подразумевает, что объект итератора отличается от тех, по которым он итерируется. Их можно объединить, используя диапазон как итератор, чтобы упростить код, как показано ниже:

Javascript range object call Symbol.iterator
let range = { from: 0, to: 5, [Symbol.iterator]() { this.currentValue = this.from; return this; }, next() { if (this.currentValue <= this.to) { return { done: false, value: this.currentValue++ }; } else { return { done: true }; } } }; for (let num of range) { console.log(num); // 0, then 1, 2, 3, 4, 5 }

Основным недостатком в этом случае является то, что невозможно иметь два for..of , которые работают с объектом одновременно.

Могут существовать бесконечные итераторы. Например, вы можете преобразовать диапазон в бесконечный с помощью range.to = Infinity. Есть и другой вариант: вы можете создать итерируемый объект, который генерирует бесконечную последовательность псевдослучайных чисел. Так что ограничений на метод next не накладывается, и он может возвращать больше значений. Следовательно, цикл for..of также станет бесконечным для такого итерируемого объекта. Но его можно остановить с помощью break.

Строки как итерируемые объекты

Наиболее часто используемыми итерируемыми объектами являются строки и массивы.

В случае строки, for..of может проходить по каждому ее символу:

Javascript strings iterables
for (let char of "W3Docs") { // triggers 6 times: once for each character console.log(char); // W, then 3, then D, then o, then c, then s }

Это будет работать правильно, как показано ниже:

Javascript strings iterables
let str = 'ȬẂ'; for (let char of str) { console.log(char); // Ȭ, and then Ẃ }

Подобие массива и итерируемые объекты

В этом разделе мы рассмотрим термины "итерируемые объекты" и "подобие массива". Они могут показаться похожими, но это очень разные вещи.

Подобные массивам считаются объектами, содержащими индексы и length. Итерируемые объекты - это объекты, способные реализовать метод Symbol.iterator . Об этом методе было описано выше.

Используя JavaScript, вы часто столкнетесь с объектами, напоминающими массивы, и итерируемыми объектами. Например, строки могут быть и итерируемыми, и похожими на массивы. Напротив, итерируемый объект никогда не может стать похожим на массив, и наоборот.

Рассмотрим пример объекта, который является похожим на массив, но не является итерируемым:

Javascript object is an array-like not an iterable
let arrLike = { // has indexes and length => array-like 0: "Welcome", 1: "to", 2: "W3Docs", length: 3 }; // Error (no Symbol.iterator) for (let item of arrLike) { console.log(item); }

В общем, и итерируемые объекты, и объекты, похожие на массивы, не являются массивами, так как они не содержат методы pop, push и так далее.

Описание Array-form

Array-form - это обобщенный метод, используемый для преобразования итерируемого объекта или объекта, похожего на массив, в настоящий массив. Затем на нем можно вызвать методы массива. Вот пример использования Array-form:

Javascript object is an array-like iterable
let arrLike = { 0: "Welcome", 1: "to", 2: "W3Docs", length: 3 }; let arr = Array.from(arrLike); // (*) console.log(arr.pop()); // W3Docs (method operates)

В строке (*), метод Array.from принимает объект, проверяет, является ли он объектом, похожим на массив или итерируемым объектом, затем создает новый массив, копируя в него все элементы. Посмотрим, как это происходит с итерируемым объектом:

Javascript array an iterable
let range = { from: 0, to: 5, [Symbol.iterator]() { this.currentValue = this.from; return this; }, next() { if (this.currentValue <= this.to) { return { done: false, value: this.currentValue++ }; } else { return { done: true }; } } }; //confirming that the range is obtained from the above example let arr = Array.from(range); console.log(arr); // 0,1,2,3,4,5 ;array toString conversion works

Полный синтаксис Array.from выглядит так:

Array.from(obj[, mapFunc, thisArg])

Второй аргумент mapFn - это функция, которая может быть применена к каждому элементу перед его добавлением в массив. Третий аргумент thisArg позволяет установить this для него. Вот пример:

Javascript object is an array-like not an iterable
let range = { from: 0, to: 5, [Symbol.iterator]() { this.currentValue = this.from; return this; }, next() { if (this.currentValue <= this.to) { return { done: false, value: this.currentValue++ }; } else { return { done: true }; } } }; // provided that the range is taken from the above example // square of each number let arr = Array.from(range, num => num * num); console.log(arr); // 0,1,4,9,16,25

Теперь давайте преобразуем строку в массив символов с помощью Array.from:

Javascript turn a string into an array of characters
let str = 'ȬẂ'; // splits the str into an array of characters let chars = Array.from(str); console.log(chars[0]); // Ȭ console.log(chars[1]); // Ẃ console.log(chars.length); // 2

В отличие от метода str.split, он зависит от итеративной природы строки.

Он действует так же, как:

Javascript turn a string into an array of characters
let str = 'ȬẂ'; let charsArr = []; // Array.from internally does the same loop for (let char of str) { charsArr.push(char); } console.log(charsArr);

Вывод

Итерируемые объекты - это объекты, которые могут использоваться в цикле for..of.

Как правило, они должны реализовывать метод Symbol.iterator. Этот метод, как правило, вызывается автоматически for..of. Но его также можно вызвать напрямую. Объекты, имеющие индексированные свойства, и length считаются похожими на массивы. Они также могут включать другие свойства и методы, но они не являются настоящими массивами. Для их преобразования в массивы можно использовать метод Array.from(obj[, mapFn, thisArg]).

Аргументы mapFn и thisArg позволяют применять функцию к каждому из них.

Время Викторины: Проверьте Свои Навыки!

отовы проверить свои знания? Погрузитесь в наши интерактивные викторины для более глубокого понимания и веселого способа закрепить знания.

Считаете ли это полезным?