# iterator 迭代器

很多内置对象实现Iterable接口:

  • String
  • Array
  • Map
  • Set
  • arguments
  • NodeList等Dom集合类型

ES6 制订了一套标准化的迭代器接口(包含 3 个方法,见下表),只要实现了这套接口都能成为迭代器。

方法 返回值 描述
next() IteratorResult 必选,获取下一个迭代器结果
return() IteratorResult 可选,停止迭代并返回一个迭代器结果
throw() IteratorResult 可选,抛出错误并返回一个迭代器结果

检查是否存在默认存在迭代器属性可以暴露[Symbol.iterator]这个工厂函数,可迭代对象:包含 Symbol.iterator 属性的对象被称为可迭代对象(Iterable), Symbol.iterator 是一个特殊的内置符号,它的值是一个返回迭代器的方法

let num = 1;
let obj = {};

console.log(num[Symbol.iterator]);  // undefined
console.log(obj[Symbol.iterator]);  // undefined

let str = 'abc';
let arr = ['a', 'b', 'c'];
let els = document.querySelectorAll('div');

// These types all have iterator factories
console.log(str[Symbol.iterator]);  // f values() { [native code] }
console.log(set[Symbol.iterator]);  // f values() { [native code] }
console.log(els[Symbol.iterator]);  // f values() { [native code] }

// Invoking the factory function produces an Iterator
console.log(str[Symbol.iterator]());  // StringIterator {}
console.log(set[Symbol.iterator]());  // SetIterator {}
console.log(els[Symbol.iterator]());  // ArrayIterator {}

下面的示例使用了数组的默认迭代,下面的示例使用了数组的默认迭代.

var arr = ["a", "b"],
iterator = arr[Symbol.iterator]();
iterator.next(); //{value: "a", done: false}
iterator.next(); //{value: "b", done: false}
iterator.next(); //{value: undefined, done: true} 

具有迭代器接口的数据,可以使用for...of/扩展运算符/Array.from/转Map/转Set等操作

let s ='abcde'
let f1=[...s]
console.log(f1)//[a,b,c,d,e]
let f2 = Array.from(s)
console.log(f2)//[a,b,c,d,e]
let f3 =new Set(s)
console.log(f3)//Set(5) {'a', 'b', 'c', 'd', 'e'}
let tems = f1.map((el,i)=>[el,i])
let f4 =new Map(tems)
console.log(f4)//Map(5) {'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4}

# 迭代器(Iterator)实现原理

for...of实现的前提是一个数据结构部署了 Symbol.iterator 属性

再看一个普通的字面量对象例子:

var obj={0:"rrr",1:"444"};
   for(var value of obj){
     console.log(value);
   }

运行报错,表明这个字面量对象是不可遍历的。那能否像Array,String等对象一样支持for-of遍历呢?答案是可以的,Array,String等对象之所以可遍历,是因为在它们的原型上,实现了迭代器( Iterator )的接口。

  • 那如何实现迭代器(Iterator)接口呢,只需要在这个对象中实现[Symbol.iterator]属性方法.
  • 最重要的是要实现next方法!!!next方法中要实现返回值{value:xx,done:xxx}
var obj={
    0:"rrr",
    1:"444",
    //实现[Symbol.iterator]属性方法
    [Symbol.iterator]:function(){
        const self = this;
        let index=0;
        return {
          next:function(){//实现next
            if(index<2){
              return {//遍历中
                value:self[index++],
                done: false//表示遍历没有结束,done设置为fasle
              }
            }else{
              return{//遍历结束
                value:undefined,//结束后,返回undefined
                done: true//done设置为true,表示遍历结束
              }
            }
          }
        }
     } 
    };
   for(var value of obj){
     console.log(value);//"rrr","444"
   }

正确的遍历了obj,打印了obj的属性的值。再来看下[Symbol.iterator]属性函数体,返回了next方法,next方法中遍历了此obj的属性对象,并返回字面量对象{value:xxx,done:xxx}这种结构对象,在遍历过程中,value返回对应属性值,且done设置为false;当遍历结束后,value返回undefined,且done设置为true。

var it = obj[Symbol.iterator]();
console.log(it.next());//{value: "rrr", done: false}
console.log(it.next());//{value: "444", done: false}
console.log(it.next());//{value: undefined, done: true}

这就是for-of的原理,每次遍历都会调用该对象的[Symbol.iterator]属性的next方法,当返回{value: undefined, done: true}后,表示遍历结束。

所以任何对象要变成可遍历的对象,只需要实现[Symbol.iterator]属性方法,定义其中的next方法即可。

# 创建可迭代对象

下面用ES5的语法创建一个迭代器

function createIterator(items) {
    var i = 0;
    return {
        next: function() {
            var done = (i >= items.length);
            var value = !done ? items[i++] : undefined;
            return {
                done: done,
                value: value
            };
        }
    };
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"

在上面这段代码中,createIterator()方法返回的对象有一个next()方法,每次调用时,items数组的下一个值会作为value返回。当i为3时,done变为true;此时三元表达式会将value的值设置为undefined。最后两次调用的结果与ES6迭代器的最终返回机制类似,当数据集被用尽后会返回最终的内容

# es6模式创建迭代器

普通的对象只要包含 Symbol.iterator 属性,并返回一个迭代器,就能摇身一变成为可迭代对象。在下面的代码中,iterable 对象本身就是迭代器,因此Symbol.iterator 属性的返回值可以是当前对象。注意,必须返回迭代器,否则会出现不可预料的错误。

var iterable = {
		items: ["a", "b"],
		index: 0,
		[Symbol.iterator]() {
			return this;
		},
		next() {
			var done = this.index >= this.items.length;
			return {
				value: this.items[this.index++],
				done: done
			};
		},
		return () {
			return {
				value: undefined,
				done: true
			};
		}
	};
	for (var value of iterable) {
		console.warn(value)
	} 
	// a 
	// b

在 iterable 对象中,包含 return()方法,只有当迭代器终止遍历时,才会触发此方法。例如,在 for-of 循环体中执行 break、continue 或 return 语句,甚至还能通过调用 throw 语句抛出自定义异常,提前结束代码的运行,以此实现触发条件,如下所示。

for (var value of iterable) {
 throw new Error();
}

# 迭代器实例不互相干扰

let arr = ['foo', 'bar'];
let iter1 = arr[Symbol.iterator](); 
let iter2 = arr[Symbol.iterator]();

console.log(iter1.next());  // { done: false, value: 'foo' }
console.log(iter2.next());  // { done: false, value: 'foo' } 
console.log(iter2.next());  // { done: false, value: 'bar' }
console.log(iter1.next());  // { done: false, value: 'bar' }
  • 迭代器并不与可迭代对象某个时刻的快照绑定,而仅仅是使用游标来记录遍历可迭代对象的历程,如果期间改变了对象,迭代器也会反映相应的变化
let arr = ['foo', 'baz'];
let iter = arr[Symbol.iterator]();

console.log(iter.next());  // { done: false, value: 'foo' }

// Insert value in the middle of array
arr.splice(1, 0, 'bar'); 

console.log(iter.next());  // { done: false, value: 'bar' }
console.log(iter.next());  // { done: false, value: 'baz' }
console.log(iter.next());  // { done: true, value: undefined }

# 提前终止迭代器

  • 当使用break或者抛出异常的时候,如果迭代器内部实现了return方法,则会执行提前终止迭代器
class Counter {
  constructor(limit) {
    this.limit = limit;
  }

  [Symbol.iterator]() {
    let count = 1,
        limit = this.limit;
    return {
      next() {
        if (count <= limit) {
          return { done: false, value: count++ };
        } else {
          return { done: true };
        }
      },
      return() {
        console.log('Exiting early');
        return { done: true };
      }
    };
  }
}

let counter1 = new Counter(5);

for (let i of counter1) {
  if (i > 2) {
    // break; //注释掉后 1 2 3 4 5
  }
  console.log(i);
}
// 1
// 2
// Exiting early


let counter2 = new Counter(5);

try {
  for (let i of counter2 ){
    if (i > 2) {
      throw 'err'; 
    }
    console.log(i);
  }
} catch(e) {}
// 1
// 2
// Exiting early


let counter3 = new Counter(5);

let [a, b] = counter3;
// Exiting early
  • 如果迭代器没有关闭,则会继续从上次离开的地方继续迭代,比如数组的迭代器就不能关闭:
let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();

for (let i of iter) {
  console.log(i);
  if (i > 2) {
    break
  }
}
// 1
// 2
// 3

for (let i of iter) {
  console.log(i);
}
// 4
// 5 

迭代器中return不一定存在,也就是并非所有的迭代器都是可关闭的,可以通过判断是否有return方法来判断是否是可关闭的迭代器对象。给不可关闭的迭代器增加一个return方法并不能让它变可关闭,这是因为调用return不会强制迭代器进入关闭状态,即便如此,return方法还是会执行。

let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();
iter.return = function(){
	console.log('退出')
	return {done:true}
}
for (let i of iter) {
  console.log(i);
  if (i > 2) {
    break
  }
}
// 1
// 2
// 3
//退出
for (let i of iter) {
  console.log(i);
}
// 4
// 5 
最后更新: 11/23/2024, 1:16:30 PM