This translation is incomplete. Please help translate this article from English.
Это экспериментальная технология, часть предложения Harmony (ECMAScript 6).
Поскольку спецификация этой технологии ещё не стабилизировалась, проверьте таблицу совместимости её использования в различных браузерах. Также обратите внимание, что синтаксис и поведение экспериментальной технологии могут быть изменены в будущих версиях браузеров в соответствии с изменениями в спецификации.
Одно из нововведений стандарта ECMAScript 6 - протоколы перебора, которые могут реализованы любым объектом, соблюдая при этом определенные правила.
Протоколы перебора
Протоколы перебора включают the "iterable" protocol и the "iterator" protocol.
Протокол "Итерируемый"
Протокол "Итерируемый" позволяет JavaScript объектам определять или настраивать поведение перебора, например, то какие значения перебираются в конструкции for..of. Некоторые встроенные типы, такие как Array или Map, имеют поведение перебора по умолчанию, в то время как другие типы (такие как Object) его не имеют
Для того, чтобы объект был итерируемым, в нем должен быть реализован метод @@iterator, т.е. этот объект (или любой из объектов из его prototype chain) должен иметь свойство с именем Symbol.iterator:
| Свойство | Значение |
|---|---|
[Symbol.iterator] |
Функция без аргументов, возвращающая объект, соответствующий iterator protocol. |
Всякий раз, когда объект подлежит перебору (например, когда в коде встречается цикл for..of), вызывается его метод @@iterator без аргументов, и возвращаемый iterator используется для получения перебираемых значений.
Протокол "Итератор"
Протокол "Итератор" определяет стандартный способ получения последовательности значений (конечной или бесконечной).
Объект является итератором, если в нем определен метод next() , реализующий следующую логику:
| Свойство | Значение |
|---|---|
next |
Функция без аргументов, возвращающая объект с двумя свойствами:
|
Some iterators are in turn iterables:
var someArray = [1, 5, 7]; var someArrayEntries = someArray.entries(); someArrayEntries.toString(); // "[object Array Iterator]" someArrayEntries === someArrayEntries[Symbol.iterator](); // true
Examples using the iteration protocols
A String is an example of a built-in iterable object:
var someString = "hi"; typeof someString[Symbol.iterator] // "function"
String's default iterator returns the string's characters one by one:
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 }
Some built-in constructs, such as the spread operator, use the same iteration protocol under the hood:
[...someString] // ["h", "i"]
We can redefine the iteration behavior by supplying our own @@iterator:
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
};
};
Notice how redefining @@iterator affects the behavior of built-in constructs, that use the iteration protocol:
[...someString] // ["bye"] someString + "" // "hi"
Builtin iterables
String, Array, TypedArray, Map and Set are all built-in iterables, because the prototype objects of them all have an @@iterator method.
User-defined iterables
We can make our own iterables like this:
var myIterable = {}
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
Builtin APIs need iterables
Map([iterable]), WeakMap([iterable]), Set([iterable]) and WeakSet([iterable]):
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
and Promise.all(iterable), Promise.race(iterable), Array.from()
Syntaxes need iterables
for-of, spread, yield*, destructing
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"
Non-well-formed iterables
If an iterable's @@iterator method doesn't return an iterator object, then it's a non-well-formed iterable, using it as such is likely to result in runtime exceptions or buggy behavior:
var nonWellFormedIterable = {}
nonWellFormedIterable[Symbol.iterator] = () => 1
[...nonWellFormedIterable] // TypeError: [] is not a function
A generator object is an iterator or an iterable?
The answer is, both are correct:
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 return its self (an iterator), so it's an well-formed iterable
[...aGeneratorObject]
// [1, 2, 3]
Examples
Simple iterator
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
Infinite iterator
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'
// ...
With a generator
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'
// ...
For more informations on ES6 generators, see the dedicated page.