详解JS迭代器

1,986 阅读4分钟

什么是迭代器?

迭代器的意义是什么?

可迭代协议

可迭代协议允许 JavaScript 对象去定义或定制它们的迭代行为, 例如(定义)在一个 for..of 结构中什么值可以被循环(得到)。

当一个对象实现了 @@iterator方法,就成为了可迭代对象。意思是这个对象(或者它原型链上的某个对象)必须有一个名字是 Symbol.iterator 的属性:

属性
[Symbol.iterator] 返回一个对象的无参数函数,被返回对象需要符合迭代器协议

当该对象需要被迭代的时(例如用于一个for..of循环中),它的@@iterator方法此时会被调用,返回的那个符合迭代器协议的对象在迭代中不断产生值。

迭代器协议

迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值,并且所有的值都已经被迭代后,就会有一个默认的返回值。

当一个对象只有满足下述条件才会被认为是一个迭代器: 它实现了一个 next() 的方法并且拥有以下含义:

属性
next 返回一个对象的无参函数,被返回对象拥有两个属性:
  • done (boolean)
    • true:迭代器已经超过了可迭代次数。这种情况下,value的值可以被省略
    • 如果迭代器可以产生序列中的下一个值,则为 false。这等效于没有指定done这个属性。
  • value - 迭代器返回的任何 JavaScript 值。done 为 true 时可省略。

next 方法必须要返回一个对象,该对象有两个必要的属性: done和value,如果返回一个非对象值(比如false和undefined) 会展示一个 TypeError ("iterator.next() returned a non-object value") 的错误

创建一个迭代器对象

var myIterator = {
    next: function() {
        let ret =  Math.random(10);
        if (ret > 0.8) {
            console.log('exit:', ret);
            return {done: true};
        }else {
            return {done: false, value: ret};
        }
    },
    [Symbol.iterator]: function() { return this }
}

for...of 循环中使用该迭代器:

for (let c of myIterator) {
    console.log(c);
}

可以看到类似以下输出:

0.02448891409447973
0.7289375899470307
0.35493549963412474
0.4402992067394058
0.3310587101417566
0.7577731432499597
0.43807458917601916
exit: 0.8322643163446177

内置可迭代对象

String, Array, TypedArray, Map and Set 是所有内置可迭代对象, 因为它们的原型对象都有一个@@iterator方法.

String为例:

String 是一个内置的可迭代的对象。

String 的默认迭代器会一个接一个返回该字符串的字符:

> let str = "hi";
undefined
> let iterator = str[Symbol.iterator]();
undefined
> iterator.next()
{ value: 'h', done: false }
> iterator.next()
{ value: 'i', done: false }
> iterator.next()
{ value: undefined, done: true }
> iterator.next()
{ value: undefined, done: true }

一些内置的语法结构,比如 spread operator (展开语法:[...val]),内部也使用了同样的迭代协议:

> [...str]
[ 'h', 'i' ]

我们可以通过自己的 @@iterator 方法重新定义迭代行为:

let s = new String("hi");
let iterator = s[Symbol.iterator]();

console.log(iterator.next());  // 'H'
console.log(iterator.next());  // 'i'
console.log(iterator.next());  // { value: undefined, done: true }

s[Symbol.iterator] = function() {
    return {
        next: function() {
            if (this._first) {
                this._first = false;
                return {value: "bye", done: false};
            }else {
                return {done: true}; 
            }
        },
        _first: true
    };
}

let iterator2 = s[Symbol.iterator]();

console.log(iterator2.next());  //{ value: 'bye', done: false }
console.log(iterator2.next());  //{ done: true }
console.log(iterator2.next());  //{ done: true }

// 不改变字符串的值
console.log(s + "");  // "Hi"
console.log(...s);  // "Bye"
  1. 字符串直接量 s = "Hi";,无法重新定义迭代行为。
  2. 重新定义迭代行为,不改变字符串的值。

几种迭代器

简单迭代器

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

无穷迭代器

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'
// ...

生成器式的迭代器

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 ... of 会发现报错,也就是不是可迭代对象。但是生成器对象既是迭代器又是可迭代对象。

function* idMaker(){
    var index = 0;
    while(true)
        yield index++;
}

var gen = idMaker();
console.log(gen.next());
console.log(gen.next());


for(let a of gen) {
    console.log(a);
}

一个良好的迭代即实现了迭代器协议,又实现了可迭代协议,方式就是可迭代协议返回的是自身,之前提到的例子就是这种情况,既是迭代器又是可迭代对象。

var myIterator = {
    next: function() {
        let ret =  Math.random(10);
        if (ret > 0.8) {
            console.log('exit:', ret);
            return {done: true};
        }else {
            return {done: false, value: ret};
        }
    },
    [Symbol.iterator]: function() { return this }
}