aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/javascript/reference/iteration_protocols/index.html
blob: 8f124ba39bc7423e6c028a84acd815575eb67bdf (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
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
---
title: 迭代协议
slug: Web/JavaScript/Reference/Iteration_protocols
tags:
  - ECMAScript 2015
  - JavaScript
  - 可迭代协议
  - 迭代
  - 迭代器
  - 迭代器协议
translation_of: Web/JavaScript/Reference/Iteration_protocols
---
<div>{{jsSidebar("More")}}</div>

<p>作为 ECMAScript 2015 的一组补充规范,迭代协议并不是新的内置实现或语法,而是<em>协议</em>。这些协议可以被任何遵循某些约定的对象来实现。</p>

<p>迭代协议具体分为两个协议:<a href="#可迭代协议">可迭代协议</a><a href="#迭代器协议">迭代器协议</a></p>

<h2 id="可迭代协议">可迭代协议</h2>

<p><strong>可迭代协议</strong>允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 {{jsxref("Statements/for...of", "for..of")}} 结构中,哪些值可以被遍历到。一些内置类型同时是<a href="#内置可迭代对象">内置可迭代对象</a>,并且有默认的迭代行为,比如 {{jsxref("Array")}} 或者 {{jsxref("Map")}},而其他内置类型则不是(比如 {{jsxref("Object")}}))。</p>

<p>要成为<strong>可迭代</strong>对象, 一个对象必须实现 <code><strong>@@iterator</strong></code> 方法。这意味着对象(或者它<a href="/zh-CN/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain">原型链</a>上的某个对象)必须有一个键为 <code>@@iterator</code> 的属性,可通过常量 {{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>
    <p>一个无参数的函数,其返回值为一个符合<a href="#迭代器协议">迭代器协议</a>的对象。</p>
   </td>
  </tr>
 </tbody>
</table>

<p>当一个对象需要被迭代的时候(比如被置入一个 {{jsxref("Statements/for...of", "for...of")}} 循环时),首先,会不带参数调用它的 <code>@@iterator</code> 方法,然后使用此方法返回的<strong>迭代器</strong>获得要迭代的值。</p>

<p>值得注意的是调用此零个参数函数时,它将作为对可迭代对象的方法进行调用。 因此,在函数内部,<code>this</code>关键字可用于访问可迭代对象的属性,以决定在迭代过程中提供什么。</p>

<p>此函数可以是普通函数,也可以是生成器函数,以便在调用时返回迭代器对象。 在此生成器函数的内部,可以使用<code>yield</code>提供每个条目。</p>

<h2 id="迭代器协议"><strong>迭代器协议</strong></h2>

<p><strong>迭代器协议</strong>定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。</p>

<p>只有实现了一个拥有以下语义(semantic)的 <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>

    <dl>
     <dt><code>done</code>(boolean)</dt>
     <dd>
     <p>如果迭代器可以产生序列中的下一个值,则为 <code>false</code>。(这等价于没有指定  <code>done</code> 这个属性。)</p>

     <p>如果迭代器已将序列迭代完毕,则为 <code>true</code>。这种情况下,<code>value</code> 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。</p>
     </dd>
     <dt><code>value</code></dt>
     <dd>迭代器返回的任何 JavaScript 值。done 为 true 时可省略。</dd>
    </dl>

    <p><code>next()</code> 方法必须返回一个对象,该对象应当有两个属性: <code>done</code><code>value</code>,如果返回了一个非对象值(比如 <code>false</code><code>undefined</code>),则会抛出一个 {{jsxref("TypeError")}} 异常(<code>"iterator.next() returned a non-object value"</code>)。</p>
   </td>
  </tr>
 </tbody>
</table>

<div class="note">
<p><strong>备注:</strong>不可能判断一个特定的对象是否实现了迭代器协议,然而,创造一个<em>同时</em>满足迭代器协议和可迭代协议的对象是很容易的(如下面的示例中所示)。</p>

<p>这样做允许一个迭代器能被各种需要可迭代对象的语法所使用。因此,很少会只实现迭代器协议,而不实现可迭代协议。</p>

<pre class="brush: js example-good notranslate"><code>var myIterator = {
    next: function() {
        // ...
    },
    [Symbol.iterator]: function() { return this }
}</code></pre>
</div>

<h2 id="使用迭代协议的例子">使用迭代协议的例子</h2>

<p>{{jsxref("String")}} 是一个内置的可迭代对象:</p>

<pre class="brush: js notranslate">let someString = "hi";
typeof someString[Symbol.iterator];          // "function"
</pre>

<p><code>String</code> 的默认迭代器会依次返回该字符串的各码点(code point):</p>

<pre class="brush: js notranslate">let 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>一些内置的语法结构——比如{{jsxref("Operators/Spread_operator", "展开语法")}}——其内部实现也使用了同样的迭代协议:</p>

<pre class="brush: js notranslate">[...someString]                              // ["h", "i"]</pre>

<p>我们可以通过提供自己的 <code>@@iterator</code> 方法,重新定义迭代行为:</p>

<pre class="brush: js notranslate">// 必须构造 String 对象以避免字符串字面量 auto-boxing
var someString = new String("hi");
someString[Symbol.iterator] = function() {
  return { // 只返回一次元素,字符串 "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 notranslate">[...someString];                              // ["bye"]
someString + "";                              // "hi"
</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 notranslate">var myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};
[...myIterable]; // [1, 2, 3]
</pre>

<h3 id="接受可迭代对象的内置_API">接受可迭代对象的内置 API</h3>

<p>很多 API 接受可迭代对象作为参数,例如:</p>

<ul>
 <li>{{jsxref("Map", "new Map([<var>iterable</var>])")}}</li>
 <li>{{jsxref("WeakMap", "new WeakMap([<var>iterable</var>])")}}</li>
 <li>{{jsxref("Set", "new Set([<var>iterable</var>])")}}</li>
 <li>{{jsxref("WeakSet", "new WeakSet([<var>iterable</var>])")}}</li>
</ul>

<pre class="notranslate">new Map([[1, 'a'], [2, 'b'], [3, 'c']]).get(2); // "b"

let myObj = {};

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>

<h4 id="另外,还有…">另外,还有…</h4>

<ul>
 <li>{{jsxref("Promise.all()", "Promise.all(<var>iterable</var>)")}}</li>
 <li>{{jsxref("Promise.race()", "Promise.race(<var>iterable</var>)")}}</li>
 <li>{{jsxref("Array.from()", "Array.from(<var>iterable</var>)")}}</li>
</ul>

<h3 id="需要可迭代对象的语法">需要可迭代对象的语法</h3>

<p>一些语句和表达式需要可迭代对象,比如 {{jsxref("Statements/for...of", "for...of")}} 循环、{{jsxref("Operators/Spread_syntax", "展开语法")}}{{jsxref("Operators/yield*", "yield*")}},和{{jsxref("Operators/Destructuring_assignment", "解构赋值")}}</p>

<pre class="brush: js notranslate">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="格式不佳的可迭代对象">格式不佳的可迭代对象</h3>

<p>如果一个可迭代对象的 <code>@@iterator</code> 方法不能返回迭代器对象,那么可以认为它是一个<em>格式不佳</em>的(<em>Non-well-formed</em>)可迭代对象 。</p>

<p>使用这样的可迭代对象很可能会导致如下的运行时(runtime)异常,或者不可预料的表现:</p>

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

<h2 id="迭代器示例">迭代器示例</h2>

<h3 id="简单迭代器">简单迭代器</h3>

<pre class="brush: js notranslate">function makeIterator(array) {
    let nextIndex = 0;
    return {
       next: function () {
           return nextIndex &lt; array.length ? {
               value: array[nextIndex++],
               done: false
           } : {
               done: true
           };
       }
    };
}

let it = makeIterator(['哟', '呀']);

console.log(it.next().value); // '哟'
console.log(it.next().value); // '呀'
console.log(it.next().done);  // true
</pre>

<h3 id="无穷迭代器">无穷迭代器</h3>

<pre class="brush: js notranslate">function idMaker() {
    let index = 0;
    return {
       next: function() {
           return {
               value: index++,
               done: false
           };
       }
    };
}

let 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 notranslate">function* makeSimpleGenerator(array) {
    let nextIndex = 0;

    while(nextIndex &lt; array.length) {
        yield array[nextIndex++];
    }
}

let gen = makeSimpleGenerator(['哟', '呀']);

console.log(gen.next().value); // '哟'
console.log(gen.next().value); // '呀'
console.log(gen.next().done);  // true



function* idMaker() {
    let index = 0;
    while (true) {
        yield index++;
    }
}

let gen = idMaker();

console.log(gen.next().value); // '0'
console.log(gen.next().value); // '1'
console.log(gen.next().value); // '2'
// ...
</pre>

<h3 id="ES2015_类_class_中的迭代器">ES2015 类 class 中的迭代器</h3>

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

  [Symbol.iterator]() {
    // Use a new index for each iterator. This makes multiple
    // iterations over the iterable safe for non-trivial cases,
    // such as use of break or nested looping over the same iterable.
    let index = 0;

    return {
      next: () =&gt; {
        if (index &lt; this.data.length) {
          return {value: this.data[index++], done: false}
        } else {
          return {done: true}
        }
      }
    }
  }
}

const simple = new SimpleClass([1,2,3,4,5])

for (const val of simple) {
  console.log(val)   //'1' '2' '3' '4' '5'
}
</pre>

<h2 id="生成器对象到底是一个迭代器,还是一个可迭代对象?">生成器对象到底是一个迭代器,还是一个可迭代对象?</h2>

<p>{{jsxref("Generator", "生成器")}}对象既是迭代器,也是可迭代对象:</p>

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

console.log(Symbol.iterator in aGeneratorObject)
// 返回true, 因为@@iterator方法是aGeneratorObject的一个属性</pre>

<h2 id="规范">规范</h2>

<table class="standard-table">
 <thead>
  <tr>
   <th scope="col">规范</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>{{SpecName('ESDraft', '#sec-iteration', 'Iteration')}}</td>
  </tr>
 </tbody>
</table>

<h2 id="参见">参见</h2>

<ul>
 <li>若想了解更多有关 ES2015 生成器的信息,请参考 {{jsxref("Statements/function*", "the <code>function*</code>")}} 的文档。</li>
</ul>