aboutsummaryrefslogtreecommitdiff
path: root/files/uk/web/javascript/reference/протоколи_перебору/index.html
blob: d5899043cae9dd8692fc1a17845b81124493c5bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
---
title: Протоколи перебору
slug: Web/JavaScript/Reference/Протоколи_перебору
tags:
  - ECMAScript 2015
  - JavaScript
  - Ітератор
  - ітерабельний об'єкт
translation_of: Web/JavaScript/Reference/Iteration_protocols
---
<div>{{jsSidebar("More")}}</div>

<p>Пара доповнень до ECMAScript 2015 є не новими вбудованими елементами чи синтаксисом, а протоколами. Ці протоколи можуть реалізовуватись будь-яким об'єктом, що відповідає певним правилам.</p>

<p>Існують два протоколи: <a href="#Протокол_перебируваного">протокол ітерабельного об'єкта</a> і <a href="#Протокол_перебирача">протокол ітератора</a>.</p>

<h2 id="Протокол_ітерабельного_обєкта">Протокол ітерабельного об'єкта</h2>

<p>Протокол <strong>ітерабельного об'єкта</strong> дозволяє об'єктам JavaScript визначати чи налаштовувати свою ітераційну поведінку, наприклад, через які значення буде проходити цикл у конструкції {{jsxref("Statements/for...of", "for..of")}}. Деякі вбудовані типи є <a href="#Built-in_iterables">вбудованими ітерабельними об'єктами</a> з визначеною за замовчуванням ітераційною поведінкою, наприклад, {{jsxref("Array")}} або {{jsxref("Map")}}, в той час, як інші типи (такі, як {{jsxref("Object")}}) не є ітерабельними.</p>

<p>Для того, щоб бути <strong>ітерабельним</strong>, об'єкт має реалізувати метод <strong>@@iterator</strong>, тобто, цей об'єкт (або один з об'єктів у його <a href="/uk/docs/Web/JavaScript/Inheritance_and_the_prototype_chain">ланцюжку прототипів</a>) повинен мати властивість з ключем <strong>@@iterator</strong>, доступну через константу <code>{{jsxref("Symbol.iterator")}}</code>:</p>

<table class="standard-table">
 <thead>
  <tr>
   <th scope="col">Властивість</th>
   <th scope="col">Значення</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>[Symbol.iterator]</code></td>
   <td>Функція без аргументів, яка повертає об'єкт, що відповідає <a href="#The_iterator_protocol">протоколу ітератора</a>.</td>
  </tr>
 </tbody>
</table>

<p>Коли виникає необхідність перебрати об'єкт (наприклад, на початку циклу <code>for..of</code>), його метод <code>@@iterator</code> викликається без аргументів, а <strong>ітератор</strong>, який він повертає, використовується для отримання значень, що перебираються.</p>

<h2 id="Протокол_ітератора">Протокол ітератора</h2>

<p>Протокол <strong>ітератора</strong> визначає стандартний спосіб створювати послідовності значень (скінченні або нескінченні).</p>

<p>Об'єкт є ітератором, коли реалізує метод <code><strong>next()</strong></code> з наступною семантикою:</p>

<table class="standard-table">
 <tbody>
  <tr>
   <th scope="col">Властивість</th>
   <th scope="col">Значення</th>
  </tr>
  <tr>
   <td><code>next</code></td>
   <td>
    <p>Функція з нулем аргументів, яка повертає об'єкт з двома властивостями:</p>

    <ul>
     <li><code>done</code> (булеве значення)

      <ul>
       <li>Має значення <code>true</code>, якщо ітератор досяг кінця послідовності, що перебирається. В цьому випадку <code>value</code> може містити <em>значення, що повертається</em> ітератором. Значення, що повертаються, пояснюються <a href="http://www.2ality.com/2013/06/iterators-generators.html#generators-as-threads">тут</a>.</li>
       <li>Має значення <code>false</code>, якщо ітератор був здатний надати наступне значення послідовності. Це аналогічно тому, щоб взагалі не вказувати значення властивості <code>done</code>.</li>
      </ul>
     </li>
     <li><code>value</code> - будь-яке значення JavaScript, що повертає ітератор. Його можна не вказувати, коли <code>done</code> дорівнює <code>true</code>.</li>
    </ul>

    <p>Метод <code>next</code> завжди повинен повертати об'єкт з належними властивостями, в тому числі <code>done</code> та <code>value</code>. Якщо повертається значення, що не є об'єктом (наприклад, <code>false</code> чи <code>undefined</code>), буде викинуто помилку {{jsxref("TypeError")}} ("iterator.next() returned a non-object value").</p>
   </td>
  </tr>
 </tbody>
</table>

<div class="blockIndicator note">
<p>Неможливо знати, чи певний об'єкт реалізує протокол ітератора, однак, можна легко створити об'єкт, який відповідає обом протоколам, ітератора та ітерабельного об'єкта (як показано нижче у прикладі). Це дозволяє використовувати ітератор там, де очікується ітерабельний об'єкт. Тому нечасто є потреба реалізовувати протокол ітератора, не реалізуючи також протокол ітерабельного об'єкта. </p>

<pre><code>var myIterator = {
    next: function() {
        // ...
    },
    [Symbol.iterator]: function() { return this }
};</code>
</pre>
</div>

<h2 id="Приклади_застосування_протоколів_перебору">Приклади застосування протоколів перебору</h2>

<p>Об'єкт {{jsxref("String")}} є прикладом вбудованого ітерабельного об'єкта:</p>

<pre class="brush: js">var someString = '13';
typeof someString[Symbol.iterator];          // "function"
</pre>

<p><a href="/uk/docs/Web/JavaScript/Reference/Global_Objects/String/@@iterator">Вбудований ітератор </a>об'єкта <code>String</code> повертає коди символів рядка один за одним:</p>

<pre class="brush: js">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 }</pre>

<p>Деякі вбудовані конструкції, такі як <a href="/uk/docs/Web/JavaScript/Reference/Operators/Spread_syntax">оператор розпакування</a>, використовують під капотом той самий протокол перебору:</p>

<pre class="brush: js">[...someString]                              // ["1", "3"]</pre>

<p>Ми можемо перевизначити поведінку під час перебору, надавши свій власний метод <code>@@iterator</code>:</p>

<pre class="brush: js">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
  };
};
</pre>

<p>Зверніть увагу, як перевизначення методу <code>@@iterator</code> впливає на поведінку вбудованих конструкцій, що використовують протокол перебору:</p>

<pre class="brush: js">[...someString];                             // ["бувай"]
someString + '';                             // "привіт"
</pre>

<h2 id="Приклади_ітерабельних_обєктів">Приклади ітерабельних об'єктів</h2>

<h3 id="Вбудовані_ітерабельні_обєкти">Вбудовані ітерабельні об'єкти</h3>

<p>{{jsxref("String")}}, {{jsxref("Array")}}, {{jsxref("TypedArray")}}, {{jsxref("Map")}} та {{jsxref("Set")}} всі є вбудованими ітерабельними об'єктами, тому що кожний з їхніх прототипів реалізує метод <code>@@</code><code>iterator</code>.</p>

<h3 id="Створені_користувачем_ітерабельні_обєкти">Створені користувачем ітерабельні об'єкти</h3>

<p>Ми можемо створювати власні ітерабельні об'єкти наступним чином:</p>

<pre class="brush: js">var myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};
[...myIterable]; // [1, 2, 3]
</pre>

<h3 id="Вбудовані_API_що_приймають_ітерабельні_обєкти">Вбудовані API, що приймають ітерабельні об'єкти</h3>

<p>Існує багато API, які приймають ітерабельні об'єкти, наприклад: {{jsxref("Map", "Map([iterable])")}}, {{jsxref("WeakMap", "WeakMap([iterable])")}}, {{jsxref("Set", "Set([iterable])")}} and {{jsxref("WeakSet", "WeakSet([iterable])")}}:</p>

<pre class="brush: js">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
</pre>

<p>Дивіться також {{jsxref("Promise.all", "Promise.all(iterable)")}}, {{jsxref("Promise.race", "Promise.race(iterable)")}} та {{jsxref("Array.from", "Array.from()")}}.</p>

<h3 id="Синтаксис_що_очікує_на_ітерабельний_обєкт">Синтаксис, що очікує на ітерабельний об'єкт</h3>

<p>Деякі оператори та вирази очікують на ітерабельні об'єкти, наприклад, цикли <code><a href="/uk/docs/Web/JavaScript/Reference/Statements/for...of">for-of</a></code>, <a href="/uk/docs/Web/JavaScript/Reference/Operators/Spread_syntax">оператор розпакування</a>, <code><a href="/uk/docs/Web/JavaScript/Reference/Operators/yield*">yield*</a></code> та <a href="/uk/docs/Web/JavaScript/Reference/Operators/Деструктуризація">деструктуризаційне присвоєння</a>:</p>

<pre class="brush: js">for(let value of ['а', 'б', 'в']){
    console.log(value);
}
// "а"
// "б"
// "в"

[...'абв']; // ["а", "б", "в"]

function* gen() {
  yield* ['а', 'б', 'в'];
}

gen().next(); // { value:"а", done:false }

[a, b, c] = new Set(['а', 'б', 'в']);
a // "а"

</pre>

<h3 id="Погано_сформовані_ітерабельні_обєкти">Погано сформовані ітерабельні об'єкти</h3>

<p>Якщо метод ітерабельного об'єкта <code>@@iterator</code> не повертає об'єкт ітератора, то це погано сформований ітерабельний об'єкт. Використання його в такому вигляді ймовірно призведе до викидання винятків під час виконання або помилкової поведінки:</p>

<pre class="brush: js">var nonWellFormedIterable = {}
nonWellFormedIterable[Symbol.iterator] = () =&gt; 1
[...nonWellFormedIterable] // TypeError: [] is not a function
</pre>

<h2 id="Приклади_ітераторів">Приклади ітераторів</h2>

<h3 id="Простий_ітератор">Простий ітератор</h3>

<pre class="brush: js">function makeIterator(array) {
    var nextIndex = 0;

    return {
       next: function() {
           return nextIndex &lt; 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
</pre>

<h3 id="Нескінченний_ітератор">Нескінченний ітератор</h3>

<pre class="brush: js">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'
// ...
</pre>

<h3 id="З_генератором">З генератором</h3>

<pre class="brush: js">function* makeSimpleGenerator(array) {
    var nextIndex = 0;

    while (nextIndex &lt; 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'
// ...
</pre>

<h3 id="З_класом_ES2015">З класом ES2015</h3>

<pre class="brush: js">class SimpleClass {
  constructor(data) {
    this.index = 0;
    this.data = data;
  }

  [Symbol.iterator]() {
    return {
      next: () =&gt; {
        if (this.index &lt; 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'
}
</pre>

<h2 id="Генератор_є_ітератором_чи_ітерабельним_обєктом">Генератор є ітератором чи ітерабельним об'єктом?</h2>

<p><a href="/uk/docs/Web/JavaScript/Reference/Global_Objects/Generator">Об'єкт генератор</a> є одночасно ітератором та ітерабельним об'єктом:</p>

<pre class="brush: js">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]
</pre>

<h2 id="Специфікації">Специфікації</h2>

<table class="standard-table">
 <tbody>
  <tr>
   <th scope="col">Специфікація</th>
   <th scope="col">Статус</th>
   <th scope="col">Коментар</th>
  </tr>
  <tr>
   <td>{{SpecName('ES2015', '#sec-iteration', 'Iteration')}}</td>
   <td>{{Spec2('ES2015')}}</td>
   <td>Початкова виознака.</td>
  </tr>
  <tr>
   <td>{{SpecName('ESDraft', '#sec-iteration', 'Iteration')}}</td>
   <td>{{Spec2('ESDraft')}}</td>
   <td></td>
  </tr>
 </tbody>
</table>

<h2 id="Див._також">Див. також</h2>

<ul>
 <li>Більше інформації щодо генераторів ES2015 дивіться у <a href="/uk/docs/Web/JavaScript/Reference/Statements/function*">документації по function*</a>.</li>
</ul>