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.
The iterable 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 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ư Array
hoặc Map
, trong khi các kiểu khác (chẳng hạn 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ố 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 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
.
The iterator protocol
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; }
};
Examples using the iteration protocols
String
là một ví dụ tích hợp sẵn iterable object:
let someString = 'hi';
console.log(typeof someString[Symbol.iterator]); // "function"
iterator mặc định 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 spread syntax—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"
Iterable examples
Built-in iterables
String
, Array
, TypedArray
, Map
, and Set
are all built-in iterables, because each of their prototype objects implements an @@iterator
method.
User-defined iterables
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]
Built-in APIs accepting iterables
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
See Also
Syntaxes expecting iterables
Some statements and expressions expect iterables, for example the for...of
loops, the spread operator)}}, yield*
, and 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"
Non-well-formed iterables
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
Iterator examples
Simple iterator
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
Infinite iterator
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'
// ...
With a generator
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'
// ...
With ES2015 class
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'
}
Is a generator object an iterator or an iterable?
A generator object 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]
Specifications
See also
- To learn more about ES2015 generators, see:
thefunction*
documentation