--- title: Iteration protocols slug: Web/JavaScript/Reference/Iteration_protocols translation_of: Web/JavaScript/Reference/Iteration_protocols ---
Là một vài bổ sung cho ECMAScript 2015, Iteration protocols không phải là một tích hợp sẵn hay cú pháp mới mà là protocols. Các giao thức này có thể triển khai bởi bất kỳ đối tượng nào đơn giản bằng cách thực hiện theo một số quy ước.
Có 2 giao thức: iterable protocol và iterator protocol.
Iterable protocol cho phép các đối tượng của JavaScript xác định hoặc tuỳ chỉnh hành vi lặp của chúng, chẳng hạn như giá trị nào sẽ được lặp trong vòng lặp {{jsxref("Statements/for...of", "for...of")}}. Một số kiểu tích hợp sẵn built-in iterables là hành vi lặp mặc định, chẳng hạn như {{jsxref("Array")}} hoặc {{jsxref("Map")}}, trong khi các kiểu khác (chẳng hạn {{jsxref("Object")}}) không có.
Để có thể triển khai giao thức iterable, một đối tượng phải có phương thức @@iterator
, điều này có nghĩa là một đối tượng (hoặc một trong các đối tượng trên prototype chain của đối tượng đó) phải có một thuộc tính @@iterator
và thao tác với thuộc tính đó thông qua hằng số {{jsxref("Symbol.iterator")}}:
Property | Value |
---|---|
[Symbol.iterator] |
Một hàm không có tham số đầu vào trả ra một đối tượng phù hợp với iterator protocol. |
Bất cứ khi nào một đối tượng thực hiện vòng lặp (chẳng hạn như sử dụng vòng lặp {{jsxref("Statements/for...of", "for...of")}}), phương thức @@iterator
sẽ được gọi mà không có tham số đầu vào, và trả ra iterator được sử dụng để thu được giá trị được lặp.
Lưu ý khi hàm không tham số đầu vào được gọi, nó sẽ được gọi như là một phương thức của iterable object. Do đó bên trong hàm, từ khoá this
có thể được sử dụng để truy cập vào các thuộc tính của iterable object, để quyết định những gì được cung cấp trong quá trình lặp.
Hàm này có thể là một hàm bình thường hoặc nó có thể là một generator function, do đó khi được gọi, một iterator object sẽ được trả về. Bên trong của generator function, mỗi giá trị trả về có thể cung cấp bằng cách sử dụng yield
.
Ierator protocol định nghĩa một cách tiêu chuẩn để tạo ra một chuỗi các giá trị (hưu hạn hoặc vô hạn), và có thể trả về 1 giá trị khi tất cả các giá trị đã được tạo.
Một đối tượng là một iterator khi nó triển khai phương thức next()
với ý nghĩa như sau:
Property | Value |
---|---|
next() |
Một hàm không tham số đầu vào trả ra một đối tượng có ít nhất 2 thuộc tính sau:
Phương thức |
Lưu ý: Không thể biết liệu một đối tượng cụ thể có triển khai giao thức iterator hay không. Tuy nhiên, dễ dàng để tạo ra một đối tượng mà có cả 2 giao thức iterator và iterable (như ví dụ dưới đây).
Làm như vậy cho phép một iterator có thể sử dụng các cú pháp đa dạng của iterables. Vì vậy, rất hiếm khi triển khai giao thức Iterator Protocol mà không triển khai Iterable.
// Satisfies both the Iterator Protocol and Iterable let myIterator = { next: function() { // ... }, [Symbol.iterator]: function() { return this; } };
{{jsxref("String")}} là một ví dụ tích hợp sẵn iterable object:
let someString = 'hi'; console.log(typeof someString[Symbol.iterator]); // "function"
{{jsxref("String/@@iterator", "iterator mặc định", "", 1)}} của String
trả ra lần lượt từng mã của các ký tự:
let iterator = someString[Symbol.iterator](); console.log(iterator + ''); // "[object String Iterator]" console.log(iterator.next()); // { value: "h", done: false } console.log(iterator.next()); // { value: "i", done: false } console.log(iterator.next()); // { value: undefined, done: true }
Some built-in constructs—such as the {{jsxref("Operators/Spread_operator", "spread syntax", "", 1)}}—use the same iteration protocol under the hood:
console.log([...someString]); // ["h", "i"]
You can redefine the iteration behavior by supplying our own @@iterator
:
// need to construct a String object explicitly to avoid auto-boxing let someString = new String('hi'); someString[Symbol.iterator] = function () { return { // this is the iterator object, returning a single element (the string "bye") next: function () { return this._first ? { value: 'bye', done: (this._first = false) } : { done: true } }, _first: true }; };
Notice how redefining @@iterator
affects the behavior of built-in constructs that use the iteration protocol:
console.log([...someString]); // ["bye"] console.log(someString + ''); // "hi"
{{jsxref("String")}}, {{jsxref("Array")}}, {{jsxref("TypedArray")}}, {{jsxref("Map")}}, and {{jsxref("Set")}} are all built-in iterables, because each of their prototype objects implements an @@iterator
method.
You can make your own iterables like this:
let myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; console.log([...myIterable]); // [1, 2, 3]
There are many APIs that accept iterables. Some examples include:
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
Some statements and expressions expect iterables, for example the {{jsxref("Statements/for...of", "for...of")}} loops, the {{jsxref("Operators/Spread_syntax", "spread operator", "", 1)}})}}, {{jsxref("Operators/yield*", "yield*")}}, and {{jsxref("Operators/Destructuring_assignment", "destructuring assignment")}}:
for (let value of ['a', 'b', 'c']) { console.log(value); } // "a" // "b" // "c" console.log([...'abc']); // ["a", "b", "c"] function* gen() { yield* ['a', 'b', 'c']; } console.log(gen().next()); // { value: "a", done: false } [a, b, c] = new Set(['a', 'b', 'c']); console.log(a); // "a"
If an iterable's @@iterator
method doesn't return an iterator object, then it's considered a non-well-formed iterable.
Using one is likely to result in runtime errors or buggy behavior:
let nonWellFormedIterable = {}; nonWellFormedIterable[Symbol.iterator] = () => 1; [...nonWellFormedIterable]; // TypeError: [] is not a function
function makeIterator(array) { let nextIndex = 0 return { next: function() { return nextIndex < array.length ? { value: array[nextIndex++], done: false } : { done: true }; } }; } let it = makeIterator(['yo', 'ya']); console.log(it.next().value); // 'yo' console.log(it.next().value); // 'ya' console.log(it.next().done); // true
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' // ...
function* makeSimpleGenerator(array) { let nextIndex = 0; while (nextIndex < array.length) { yield array[nextIndex++]; } } let gen = makeSimpleGenerator(['yo', 'ya']); console.log(gen.next().value); // 'yo' console.log(gen.next().value); // 'ya' 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' // ...
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: () => { if (index < 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' }
A {{jsxref("Generator", "generator object", "", 1)}} is both iterator and iterable:
let aGeneratorObject = function* () { yield 1; yield 2; yield 3; }(); console.log(typeof aGeneratorObject.next); // "function", because it has a next method, so it's an iterator console.log(typeof aGeneratorObject[Symbol.iterator]); // "function", because it has an @@iterator method, so it's an iterable console.log(aGeneratorObject[Symbol.iterator]() === aGeneratorObject); // true, because its @@iterator method returns itself (an iterator), so it's an well-formed iterable console.log([...aGeneratorObject]); // [1, 2, 3]
Specification |
---|
{{SpecName('ESDraft', '#sec-iteration', 'Iteration')}} |
function*
documentation", "", 1)}}