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
|
---
title: 迭代協議
slug: Web/JavaScript/Reference/Iteration_protocols
tags:
- ECMAScript 2015
- Intermediate
- Iterator
- JavaScript
translation_of: Web/JavaScript/Reference/Iteration_protocols
---
<div>{{jsSidebar("More")}}</div>
<p>為 ECMAScript 2015 中的一些補充內容,並非新的內建物件或語法,而是協議。這些協議可被任何遵守特定協定的物件所實作。</p>
<p>本文介紹兩種協議:<a href="#The_iterable_protocol">可迭代協議(iterable protocol)</a>以及<a href="#The_iterator_protocol">迭代器協議(iterator protocol)</a>。</p>
<h2 id="可迭代協議">可迭代協議</h2>
<p><strong>可迭代(iterable)</strong>協議允許 JavaScript 物件定義或客制他們的迭代行為,例如哪些值可在 {{jsxref("Statements/for...of", "for..of")}} 語法結構中被迭代出來。部分內建型別為擁有預設迭代行為的<a href="#Built-in_iterables">可迭代內建物件(built-in iterables)</a>,如 {{jsxref("Array")}} 或 {{jsxref("Map")}},而其他型別(如 {{jsxref("Object")}})則否。</p>
<p>為了成為<strong>可迭代的(iterable)</strong>,一個物件必須實作 <strong>@@iterator</strong> 方法,意思是這個物件(或其<a href="/zh-TW/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain">原型鏈</a>中的其中一個原型物件)必須擁有一個鍵(key)值為 <strong>@@iterator</strong>(即 {{jsxref("Symbol.iterator")}} 常數)的屬性:</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">迭代器協議(iterator protocol)</a>之物件的無引數函式。</td>
</tr>
</tbody>
</table>
<p>每當物件需要被迭代時(比如在一個開始的 <code>for..of</code> 迴圈中),物件的 <code>@@iterator</code> 方法會被以不傳入引數的方式呼叫,並會使用其回傳的<strong>迭代器(iterator)</strong>來獲得被迭代出來的值。</p>
<h2 id="迭代器協議">迭代器協議</h2>
<p><strong>迭代器(iterator)</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>。</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")}} 錯誤。</p>
</td>
</tr>
</tbody>
</table>
<div class="note">
<p>我們無法反射性的一眼看出一個特定的物件是否實作了迭代器協議,然而要建立一個同時滿足迭代器及可迭代協議的物件卻是相當容易(如下例所示)。範例的做法允許一個迭代器被各個預期其可迭代行為的語法所消費。因此很少有需要實作迭代器協議而沒有實作可迭代協議的情況。</p>
<pre class="brush: js">var myIterator = {
next: function() {
// ...
},
[Symbol.iterator]: function() { return this }
};
</pre>
</div>
<h2 id="迭代協議使用範例">迭代協議使用範例</h2>
<p>{{jsxref("String")}} 為一個可迭代內建物件(built-in iterable object)的範例:</p>
<pre class="brush: js">var someString = 'hi';
typeof someString[Symbol.iterator]; // "function"
</pre>
<p><code>String</code> 的<a href="/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/String/@@iterator">預設迭代器</a>會回傳字串中的一個一個字元:</p>
<pre class="brush: js">var iterator = someString[Symbol.iterator]();
iterator + ''; // "[object String Iterator]"
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }</pre>
<p>部分內建語法結構(built-in constructs),如 <a href="/zh-TW/docs/Web/JavaScript/Reference/Operators/Spread_operator">spread syntax</a>,其內部也使用了相同的迭代協議:</p>
<pre class="brush: js">[...someString] // ["h", "i"]</pre>
<p>我們可以藉由提供我們自己的 <code>@@iterator</code> 來重新定義迭代行為:</p>
<pre class="brush: js">var someString = new String('hi'); // need to construct a String object explicitly to avoid auto-boxing
someString[Symbol.iterator] = function() {
return { // this is the iterator object, returning a single element, the string "bye"
next: function() {
if (this._first) {
this._first = false;
return { value: 'bye', done: false };
} else {
return { done: true };
}
},
_first: true
};
};
</pre>
<p>請注意,重新定義 <code>@@iterator</code> 會影響使用迭代協議之內建語法結構的行為:</p>
<pre class="brush: js">[...someString]; // ["bye"]
someString + ''; // "hi"
</pre>
<h2 id="可迭代範例">可迭代範例</h2>
<h3 id="可迭代內建物件">可迭代內建物件</h3>
<p>{{jsxref("String")}}、{{jsxref("Array")}}、{{jsxref("TypedArray")}}、{{jsxref("Map")}} 以及 {{jsxref("Set")}} 全都是可迭代內建物件,因為他們每一個的原型物件皆實作了 <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="接受可迭代物件的內建_APIs">接受可迭代物件的內建 APIs</h3>
<p>有許多 APIs 接受可迭代物件,如:{{jsxref("Map", "Map([iterable])")}}、{{jsxref("WeakMap", "WeakMap([iterable])")}}、{{jsxref("Set", "Set([iterable])")}} 及 {{jsxref("WeakSet", "WeakSet([iterable])")}}:</p>
<pre class="brush: js">var myObj = {};
new Map([[1, 'a'], [2, 'b'], [3, 'c']]).get(2); // "b"
new WeakMap([[{}, 'a'], [myObj, 'b'], [{}, 'c']]).get(myObj); // "b"
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>部分陳述式(statements)及運算式(expressions)為預期用於可迭代物件,例如 <code><a href="/zh-TW/docs/Web/JavaScript/Reference/Statements/for...of">for-of</a></code> 迴圈、<a href="/zh-TW/docs/Web/JavaScript/Reference/Operators/Spread_operator">spread syntax</a>、<code><a href="/zh-TW/docs/Web/JavaScript/Reference/Operators/yield*">yield*</a></code>,及<a href="/zh-TW/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment">解構賦值</a>:</p>
<pre class="brush: js">for(let value of ['a', 'b', 'c']){
console.log(value);
}
// "a"
// "b"
// "c"
[...'abc']; // ["a", "b", "c"]
function* gen() {
yield* ['a', 'b', 'c'];
}
gen().next(); // { value:"a", done:false }
[a, b, c] = new Set(['a', 'b', 'c']);
a // "a"
</pre>
<h3 id="非良好的(Non-well-formed)可迭代物件">非良好的(Non-well-formed)可迭代物件</h3>
<p>假如可迭件物件的 <code>@@iterator</code> 方法不是回傳一個迭代器物件,即是非良好的(non-well-formed)可迭代物件。如以下方式使用可能會導致執行時期異常或錯誤行為:</p>
<pre class="brush: js">var nonWellFormedIterable = {}
nonWellFormedIterable[Symbol.iterator] = () => 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 < 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="搭配生成器(generator)">搭配生成器(generator)</h3>
<pre class="brush: js">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'
// ...
</pre>
<h3 id="搭配_ES2015_類別">搭配 ES2015 類別</h3>
<pre class="brush: js">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; //If we would like to iterate over this again without forcing manual update of the index
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="/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Generator">生成器物件(generator object)</a>同時為迭代器及可迭代物件:</p>
<pre class="brush: js">var aGeneratorObject = function* () {
yield 1;
yield 2;
yield 3;
}();
typeof aGeneratorObject.next;
// "function", because it has a next method, so it's an iterator
typeof aGeneratorObject[Symbol.iterator];
// "function", because it has an @@iterator method, so it's an iterable
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// true, because its @@iterator method returns itself (an iterator), so it's an well-formed iterable
[...aGeneratorObject];
// [1, 2, 3]
</pre>
<h2 id="規範">規範</h2>
<table class="standard-table">
<tbody>
<tr>
<th scope="col">Specification</th>
<th scope="col">Status</th>
<th scope="col">Comment</th>
</tr>
<tr>
<td>{{SpecName('ES2015', '#sec-iteration', 'Iteration')}}</td>
<td>{{Spec2('ES2015')}}</td>
<td>Initial definition.</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 生成器(generators)的資訊,可參考<a href="/zh-TW/docs/Web/JavaScript/Reference/Statements/function*">生成器函式 function* 文件</a>。</li>
</ul>
|