--- title: Протоколи перебору slug: Web/JavaScript/Reference/Протоколи_перебору tags: - ECMAScript 2015 - JavaScript - Ітератор - ітерабельний об'єкт translation_of: Web/JavaScript/Reference/Iteration_protocols ---
Пара доповнень до ECMAScript 2015 є не новими вбудованими елементами чи синтаксисом, а протоколами. Ці протоколи можуть реалізовуватись будь-яким об'єктом, що відповідає певним правилам.
Існують два протоколи: протокол ітерабельного об'єкта і протокол ітератора.
Протокол ітерабельного об'єкта дозволяє об'єктам JavaScript визначати чи налаштовувати свою ітераційну поведінку, наприклад, через які значення буде проходити цикл у конструкції {{jsxref("Statements/for...of", "for..of")}}. Деякі вбудовані типи є вбудованими ітерабельними об'єктами з визначеною за замовчуванням ітераційною поведінкою, наприклад, {{jsxref("Array")}} або {{jsxref("Map")}}, в той час, як інші типи (такі, як {{jsxref("Object")}}) не є ітерабельними.
Для того, щоб бути ітерабельним, об'єкт має реалізувати метод @@iterator, тобто, цей об'єкт (або один з об'єктів у його ланцюжку прототипів) повинен мати властивість з ключем @@iterator, доступну через константу {{jsxref("Symbol.iterator")}}
:
Властивість | Значення |
---|---|
[Symbol.iterator] |
Функція без аргументів, яка повертає об'єкт, що відповідає протоколу ітератора. |
Коли виникає необхідність перебрати об'єкт (наприклад, на початку циклу for..of
), його метод @@iterator
викликається без аргументів, а ітератор, який він повертає, використовується для отримання значень, що перебираються.
Протокол ітератора визначає стандартний спосіб створювати послідовності значень (скінченні або нескінченні).
Об'єкт є ітератором, коли реалізує метод next()
з наступною семантикою:
Властивість | Значення |
---|---|
next |
Функція з нулем аргументів, яка повертає об'єкт з двома властивостями:
Метод |
Неможливо знати, чи певний об'єкт реалізує протокол ітератора, однак, можна легко створити об'єкт, який відповідає обом протоколам, ітератора та ітерабельного об'єкта (як показано нижче у прикладі). Це дозволяє використовувати ітератор там, де очікується ітерабельний об'єкт. Тому нечасто є потреба реалізовувати протокол ітератора, не реалізуючи також протокол ітерабельного об'єкта.
var myIterator = {
next: function() {
// ...
},
[Symbol.iterator]: function() { return this }
};
Об'єкт {{jsxref("String")}} є прикладом вбудованого ітерабельного об'єкта:
var someString = '13'; typeof someString[Symbol.iterator]; // "function"
Вбудований ітератор об'єкта String
повертає коди символів рядка один за одним:
var iterator = someString[Symbol.iterator](); iterator + ''; // "[object String Iterator]" iterator.next(); // { value: "1", done: false } iterator.next(); // { value: "3", done: false } iterator.next(); // { value: undefined, done: true }
Деякі вбудовані конструкції, такі як оператор розпакування, використовують під капотом той самий протокол перебору:
[...someString] // ["1", "3"]
Ми можемо перевизначити поведінку під час перебору, надавши свій власний метод @@iterator
:
var someString = new String('привіт'); // необхідно явно конструювати об'єкт String, щоб запобігти автопакуванню someString[Symbol.iterator] = function() { return { // це ітератор, що повертає єдиний елемент, рядок "бувай" next: function() { if (this._first) { this._first = false; return { value: 'бувай', done: false }; } else { return { done: true }; } }, _first: true }; };
Зверніть увагу, як перевизначення методу @@iterator
впливає на поведінку вбудованих конструкцій, що використовують протокол перебору:
[...someString]; // ["бувай"] someString + ''; // "привіт"
{{jsxref("String")}}, {{jsxref("Array")}}, {{jsxref("TypedArray")}}, {{jsxref("Map")}} та {{jsxref("Set")}} всі є вбудованими ітерабельними об'єктами, тому що кожний з їхніх прототипів реалізує метод @@
iterator
.
Ми можемо створювати власні ітерабельні об'єкти наступним чином:
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable]; // [1, 2, 3]
Існує багато API, які приймають ітерабельні об'єкти, наприклад: {{jsxref("Map", "Map([iterable])")}}, {{jsxref("WeakMap", "WeakMap([iterable])")}}, {{jsxref("Set", "Set([iterable])")}} and {{jsxref("WeakSet", "WeakSet([iterable])")}}:
var myObj = {}; new Map([[1, 'а'], [2, 'б'], [3, 'в']]).get(2); // "б" new WeakMap([[{}, 'а'], [myObj, 'б'], [{}, 'в']]).get(myObj); // "б" new Set([1, 2, 3]).has(3); // true new Set('123').has('2'); // true new WeakSet(function* () { yield {}; yield myObj; yield {}; }()).has(myObj); // true
Дивіться також {{jsxref("Promise.all", "Promise.all(iterable)")}}, {{jsxref("Promise.race", "Promise.race(iterable)")}} та {{jsxref("Array.from", "Array.from()")}}.
Деякі оператори та вирази очікують на ітерабельні об'єкти, наприклад, цикли for-of
, оператор розпакування, yield*
та деструктуризаційне присвоєння:
for(let value of ['а', 'б', 'в']){ console.log(value); } // "а" // "б" // "в" [...'абв']; // ["а", "б", "в"] function* gen() { yield* ['а', 'б', 'в']; } gen().next(); // { value:"а", done:false } [a, b, c] = new Set(['а', 'б', 'в']); a // "а"
Якщо метод ітерабельного об'єкта @@iterator
не повертає об'єкт ітератора, то це погано сформований ітерабельний об'єкт. Використання його в такому вигляді ймовірно призведе до викидання винятків під час виконання або помилкової поведінки:
var nonWellFormedIterable = {} nonWellFormedIterable[Symbol.iterator] = () => 1 [...nonWellFormedIterable] // TypeError: [] is not a function
function makeIterator(array) { var nextIndex = 0; return { next: function() { return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {done: true}; } }; } var it = makeIterator(['yo', 'ya']); console.log(it.next().value); // 'yo' console.log(it.next().value); // 'ya' console.log(it.next().done); // true
function idMaker() { var index = 0; return { next: function(){ return {value: index++, done: false}; } }; } var it = idMaker(); console.log(it.next().value); // '0' console.log(it.next().value); // '1' console.log(it.next().value); // '2' // ...
function* makeSimpleGenerator(array) { var nextIndex = 0; while (nextIndex < array.length) { yield array[nextIndex++]; } } var gen = makeSimpleGenerator(['yo', 'ya']); console.log(gen.next().value); // 'yo' console.log(gen.next().value); // 'ya' console.log(gen.next().done); // true function* idMaker() { var index = 0; while (true) yield index++; } var gen = idMaker(); console.log(gen.next().value); // '0' console.log(gen.next().value); // '1' console.log(gen.next().value); // '2' // ...
class SimpleClass { constructor(data) { this.index = 0; this.data = data; } [Symbol.iterator]() { return { next: () => { if (this.index < this.data.length) { return {value: this.data[this.index++], done: false}; } else { this.index = 0; //Якщо ми хотіли б перебрати його знову, без примусового ручного оновлення індексу return {done: true}; } } } }; } const simple = new SimpleClass([1,2,3,4,5]); for (const val of simple) { console.log(val); //'0' '1' '2' '3' '4' '5' }
Об'єкт генератор є одночасно ітератором та ітерабельним об'єктом:
var aGeneratorObject = function* () { yield 1; yield 2; yield 3; }(); typeof aGeneratorObject.next; // "function", бо він має метод next, отже, він ітератор typeof aGeneratorObject[Symbol.iterator]; // "function", бо він має метод @@iterator, отже, він ітерабельний об'єкт aGeneratorObject[Symbol.iterator]() === aGeneratorObject; // true, бо його метод @@iterator повертає себе (ітератор), // отже, він добре сформований ітерабельний об'єкт [...aGeneratorObject]; // [1, 2, 3]
Специфікація | Статус | Коментар |
---|---|---|
{{SpecName('ES2015', '#sec-iteration', 'Iteration')}} | {{Spec2('ES2015')}} | Початкова виознака. |
{{SpecName('ESDraft', '#sec-iteration', 'Iteration')}} | {{Spec2('ESDraft')}} |