--- title: Протоколи перебору slug: Web/JavaScript/Reference/Iteration_protocols tags: - ECMAScript 2015 - JavaScript - Ітератор - ітерабельний об'єкт translation_of: Web/JavaScript/Reference/Iteration_protocols original_slug: Web/JavaScript/Reference/Протоколи_перебору ---
Пара доповнень до 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')}} |