Iterator

Iterator(遍历器)的概念

遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。

Iterator的作用

  1. 为各种数据结构提供一个统一的、简便的访问接口
  2. 使得数据结构的成员能够按某种次序排列
  3. ES6创造了一种新的遍历命令(for...of循环),Iterator结构主要提供for...of使用

Iterator的遍历过程

  1. 创建一个指针对象,指向当前数据结构的起始位置(即遍历器对象本质是一个指针对象)
  2. 第一次调用用指针对象的next方法,可以将指针指向数据结构的第一个成员
    • 返回成员格式为包含value和done两个属性的对象。其中value是当前成员的值,done属性是布尔值,标示遍历是否结束。
  3. 第二次调用指针对象的next方法,可以将指针指向数据结构的第二个成员
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置
// 模拟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};
    }
  };
}

备注:

数据结构的默认Iterator接口

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接口的场合

一些场合会默认调用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 }

字符串的Iterator接口

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"]

Iterator接口与Generator函数

// 使用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
}

遍历器对象的return()和throw()方法

如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。

return

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

throw方法主要是配合Generator函数使用,一般的遍历器对象用不到这个方法

for...of循环

数组

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结构

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

//  DOM NodeList对象
let h3s = document.querySelectorAll("h3");
for (let h of h3s) {
   h.style.color = 'red';
}

arguments

// 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
}

与其它遍历语法的比较

参考文献

  1. ECMAScript 6 入门