遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。
// 模拟next方法返回值
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
let arr = ['a', 'b', 'c'];
// arr数组具有遍历器接口,部署在arr的Symbol.iterator属性上
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
// 类部署Iterator接口的写法。
class RangeIterator{
constructor(start, stop){
this.value = start;
this.stop = stop;
}
// Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器对象
[Symbol.iterator](){ return this; }
next(){
var value = this.value;
if( value < this.stop ){
this.value ++;
return {done: false, value: value };
} else {
return { done : true, value: undefined };
}
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for(var value of range(0, 3)){
console.log(value); //依次输出0,1,2
}
// 通过遍历器实现指针结构
function Obj(value){
this.value = value;
this.next = null;
}
Obj.prototype[Symbol.iterator] = function(){
var iterator = {
next: next
};
var current = this;
function next(){
if (current) {
var value = current.value;
current = current.next;
return {
done: false,
value: value
}
} else {
return {
done : true
};
}
}
return iterator;
};
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for(var i of one){
console.log(i); // 依次输出1,2,3,undefined
}
// 为对象添加Iterator接口
let obj = {
data: ['hello', 'world'],
[Symbol.iterator](){
const self = this;
let index = 0;
return {
next(){
if(index < self.data.length){
return {
value: self.data[index++],
done: false
}
} else {
return { value: undefined, done: true };
}
}
}
}
};
for(o of obj){
console.log(o); // 依次输出hello、world、undefined
}
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
// 类数组对象调用数组的Symbol.iterator方法
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
// 若没有[Symbol.iterator]属性,则会报错
var obj = {};
obj[Symbol.iterator] = () => 1;
[...obj] // TypeError: obj[Symbol.iterator] is not a function
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
var x = $result.value;
// ...
$result = $iterator.next();
}
一些场合会默认调用Iterator接口(即Symbol.iterator方法)
let set = new Set().add('a').add('b').add('c');
let [x,y] = set; // x='a'; y='b'
let [first, ...rest] = set; //first:"a";rest:["b", "c"]
var str = 'hello';
[...str]; // ['h','e','l','l','o']
let arr = ['b', 'c'];
['a', ...arr, 'd']; // ["a", "b", "c", "d"]
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
var someString = "hi";
typeof someString[Symbol.iterator]; // "function"
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
var str = new String("hi");
[...str] // ["h", "i"]
str[Symbol.iterator] = function() {
return {
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
[...str] // ["bye"]
// 使用generator
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
// 简写
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};
for (let x of obj) {
console.log(x); // 依次输出hello,world
}
如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。
let obj = {
data: ['hello', 'world'],
[Symbol.iterator](){
const self = this;
let index = 0;
return {
next(){
if(index < self.data.length){
return {
value: self.data[index++],
done: false
}
} else {
return { value: undefined, done: true };
}
},
return(){
console.log('return');
return { done: true };
}
}
}
};
for(o of obj){
console.log(o); // 依次输出hello、return
break;
}
throw方法主要是配合Generator函数使用,一般的遍历器对象用不到这个方法
const arr = ['red', 'green', 'blue'];
let iterator = arr[Symbol.iterator]();
for(let v of arr) {
console.log(v); // red green blue
}
// 同上for...of方法
for(let v of iterator) {
console.log(v); // red green blue
}
const arr = ['red', 'green', 'blue'];
arr.forEach(function (element, index) {
console.log(element); // red green blue
console.log(index); // 0 1 2
});
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
Set和Map结构也原生具有Iterator接口,可以直接使用for...of循环
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e); // Gecko,Trident,Webkit
}
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
console.log(name + ": " + value); // edition: 6;committee: TC39;standard: ECMA-262
}
// DOM NodeList对象
let h3s = document.querySelectorAll("h3");
for (let h of h3s) {
h.style.color = 'red';
}
// arguments对象
function printArgs() {
for (let x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
并不是所有的类数组对象都具有iterator接口,因此最简单的方法就是使用Array.from方法将其转换为数组
let arrayLike = { length: 2, 0: 'a', 1: 'b' };
// 报错
for (let x of arrayLike) {
console.log(x);
}
// 正确
for (let x of Array.from(arrayLike)) {
console.log(x);
}
对于普通对象,for...of结构不能直接使用,会报错,必须部署了iterator接口后才能使用。
var someObject = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
};
for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}
function* entries(someObject) {
for (let key of Object.keys(someObject)) {
yield [key, someObject[key]];
}
}
for (let [key, value] of entries(someObject)) {
console.log(key, "->", value);
//edition -> 6; committee -> TC39;standard -> ECMA-262
}