了解ES6中的Iterator迭代器

写在前面

迭代器是带有特殊接口的对象,用以为不同的数据结构提供统一的访问机制
由于目前ES6版本有4种可遍历的“集合”(Array,Object,Map,Set,其中三种自带默认迭代器),所以拥有Iterator之后一切都变的简单了许多。我们只需要为其部署Iterator接口即可遍历

遍历过程

  • 创建一个指针对象,指向当前数据结构的起始位置
  • 第一次调用只针对象的next方法,将指针指向数据结构的第一个成员
  • 。。。。
  • 直至数据结构的结束位置
    每次调用next方法都会返回数据结构的当前成员信息,即一个包含value和done两个属性的对象。value为对应成员值,而done则表示遍历是否结束

在网上找了一段ES5模拟Interator的代码:

function createIterator(ary) {
  var i = 0;
  return {
    next: function() {
      return {
        value: ary[i++],
        done: i > ary.length
      }
    }
  }
}
var iterator = createIterator(['a', 'b', 'c'])
var done = false;

while (!done) {
  var result = iterator.next();
  console.log(result);
  done = result.done;
}
//{ value: 'a', done: false }
//{ value: 'b', done: false }
//{ value: 'c', done: false }
//{ value: undefined, done: true }

Ps:这里只是模拟了Array数据类型的遍历,真正的Iterator会有一个类型识别的(小声BB)

有没有让大家想起当年学习C语言时候的链表?用指针把一串地址串联起来,依次访问下一个元素,这也是迭代器的本质

可迭代类型

由于ES6种引入了新的Symbol对象(唯一性),定义了一个Symbol.iterator属性,所以只要对象中含有这个属性那么都是可迭代的。之所以前边提到的四种集合均可迭代,就是因为他们都有默认的迭代器

让我们举一个栗子:

let ary = ['a', 'b', 'c'];
let Iterator = ary[Symbol.iterator]();
console.log(iterator.next()); //{ value: 'a', done: false }
console.log(iterator.next()); //{ value: 'b', done: false }
console.log(iterator.next()); //{ value: 'c', done: false }
console.log(iterator.next()); //{ value: undefined, done: true }

如图,我们将数组对象的默认iterator的值赋给了let出的新的Iterator值,并且通过next不断向后取值。不过这个是ES6自带的迭代器有些老铁看了可能不服气。那咱们自己搞一个不自带迭代器的类型:Object

const obj = {
   b: 2
 }
 const a = 'a'
 obj.a = 1;
 Object.defineProperty(obj, Symbol.iterator, {
   enumerable: false,
   writable: false,
   configurable: true,
   value: function () {
     const that = this;		//保存指针
     let index = 0;
     const ks = Object.keys(that);	
     return {
       next: function() {
         return {
           value: that[ks[index++]],
           done: (index > ks.length)
         }
       }
     }
   }
 })
 for(const v of obj) {
   console.log(v); //  2 , 1
 }

遍历之时我们要做的就是通过Object.keys去除他的key值,然后通过next向后查询。由于它被设置了[Symbol.iterator]属性,所以for…of可以找到并调用

对于类似于数组的对象,即存在数值键名以及length属性,部署Iterator接口有一个更加简便的方法:使用Symbol.iterator方法直接引用数组的Iterator接口

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

上面两种方法都可以实现。NodeList对象是类似数组的对象,本来就具有遍历接口,可以直接遍历。上图我们将其便利接口改成数组的Symbol.iterator属性不会有任何影响

事实上,Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器对象。本质上还是通过指针结构的遍历。我们最后再把它的指针结构模拟一下

function Obj(value){
	this.value = value;
	this.next = next;
}
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
}

上边代码我们首先在原型链上部署Symbol.iterator方法,调用该方法会返回遍历器对象iterator,调用该对象的next方法,在返回一个值得同时自动将内部指针移到下一个实例

Import Points

经典用法。

当数据集合非常大的时候,使用iterator可以帮助我们省去很多麻烦。

const fibonacci = {
   [Symbol.iterator]: function () {
     let [pre, next] = [0, 1];
     return {
       next() {
         [pre, next] = [next, pre + next];
         return {
           value: next,
           done: next > 1000
         }
       }
     }
   }
 }
 
 for(var n of fibonacci) {
   console.log(n)
 }
 // 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,987

Ps:[Symbol.iterator]是一个预定义好的,Symbol类型的特殊值
此值为value和done组成的键值对
我们举一个阮一峰老师的栗子

let arr = ['a', 'b', 'c'];
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接口

var str = 'hello';
[...str]		//['h', 'e', 'l', 'l', 'o']

let arr = ['b', 'c'];
['a', ...arr, 'd']		//['a', 'b', 'c', 'd']

实际上这提供了一种简便机制,可以将任何部署了Iterator接口的数据结构转化为数组
eg:let arr = […iterable];

yield*

yield*后面跟的是一个可遍历结构,亦会调用该结构的遍历器接口

let generator = function* (){
	yield 1;
	yield* [2,3,4];
	yield 5;
};

var iterator = generator();

iterator.next(); //{value: 1, donne:false};
iterator.next(); //{value: 2, donne:false};
iterator.next(); //{value: 3, donne:false};
iterator.next(); //{value: 4, donne:false};
iterator.next(); //{value: 5, donne:false};
iterator.next(); //{value: undefined, donne:true}

最后说两句

Symbol.iterator方法的最简单实现还是使用今后要介绍道的Generator函数,通过yield命令给出每一步的返回值,从而实现一个生成器。具体的话我们下一篇博客再详细说说吧

猜你喜欢

转载自blog.csdn.net/qq_38722097/article/details/83592565