ES6学习笔记(六)Iterator接口

在ES6中,添加了Set和Map结构(ES6学习笔记(五)Set结构和Map结构),加上之前的数组和对象,一共有四种可以表示“集合”的数据结构,而通过组合使用它们可以定义自己的数据结构。为了处理不同的数据结构,需要一个统一的机制,而Iterator接口就是这样的机制。

Iterator接口的作用和遍历过程


作用:Iterator接口用于为不同的数据结构提供统一的接口,使得数据结构的成员能按一定的次序遍历,部署了Iterator接口的数据结构可以使用for...of来遍历。即Iterator接口就是为了实现不同数据结构的遍历,而遍历又是有序的,所以没有部署Iterator接口的数据结构成员在部署Iterator接口后就从无序变为线性有序了。

遍历过程:Iterator接口的遍历过程是通过指针来进行的,它首先将指针指向第一个位置,然后通过遍历器中的next方法来将指针移动到下一个位置(部署Iterator接口时一定要部署next方法),通过done(布尔值)来判断是否已经遍历到尾部,当done为true时,遍历结束。

下面是一个遍历器的模拟方法

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

makeIterator方法用来模拟一个遍历器,一开始将指针指向下标为0的位置,每次调用next方法移动到下一个位置前判断当前是否到了数组的尾部,如果还没有,返回value为下一个值,done为false的对象,如果到了尾部,返回value为undefined,done为true的对象。

默认的Iterator接口


默认的Iterator接口部署在Symbol.Iterator属性,只要结构有这个属性,就是可以遍历的,而这个属性本身是一个方法,如果调用该方法,会返回一个遍历器,而可以通过该遍历器的next方法来遍历结构中的每个值,知道done为true

ES6中具备Iterator接口的数据结构有:Array,Map,Set,String,TypeArray,arguments对象,NodeList对象

var arr=[1,2,3];
var i=arr[Symbol.iterator]();
i.next();//{value: 1, done: false}
i.next();//{value: 2, done: false}
i.next();//{value: 3, done: false}
i.next();//{value: undefined, done: false}

部署Iterator接口


要遍历一个没有Iterator接口的数据结构,需要在Symbol.iterator属性上部署遍历器方法(或者在原型链上部署该方法)

class RangeIterator {
  constructor(start, stop) {//初始化遍历的起点和终点
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { 
    return this; 
  }//返回该遍历器

  next() {
    var value = this.value;//取得当前值
    if (value < this.stop) {//判断当前是否走到了结尾
      this.value++;
      return {done: false, value: value};
    }
    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
}

上面的代码部署了一个iterator接口,通过其Symbol.iterator方法来u后去其遍历器对象,然后用for...of来遍历该结构

function Obj(value) {
  this.value = value;
  this.next = null;//一开始的指针指向null
}
Obj.prototype[Symbol.iterator] = function() {
  var iterator = { next: next };
  var current = this;
  function next() {
    if (current) {//如果没有知道next的话,current为null,所以这里用于判断是否到了遍历的最后
      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
}

上面的代码声明了一个Obj方法,通过在原型链上的Symbol.iterator上部署iterator接口,在实例上调用Symbol.iterator方法时返回遍历器对象。

对于类似数组,可以直接将其Symbol.iterator方法改为直接引用数组的Iterator接口

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

// 或者NodeList.prototype[Symbol.iterator] = [][Symbol.iterator]

普通对象部署数组的Symbol.iterator方法,并无效果

Object.prototype[Symbol.iterator]=Array.prototype[Symbol.iterator];
let o=new Object({a:1,b:2});
for (let i of o)
    console.log(i);//undefined

上面的代码虽然给Object对象部署了数组的Symbol.iterator方法,但是用for...of遍历时返回了undefined。

给Symbol.iterator部署方法时要注意,返回的必须是一个遍历器,如果返回了其他结构或者没有返回,浏览器会报错。

let o={a:1}
o[Symbol.iterator]=()=>1;
for (let i of o)
    console.log(i);

自动调用iterator接口


在部署iterator接口后的数据结构和有默认iterator接口的数据在使用中,其实有些地方会自动调用到iterator接口

1.解构赋值

在对数组和Set结构进行解构赋值时,会默认调用Symbol.Iterator方法,我的理解是,数组和Set结构要进行解构赋值,需要获取里面每一个值,需要使用遍历操作,所以会默认调用Symbol.Iterator方法。

2.扩展运算符

扩展运算符是通过iterator接口遍历数据结构,将里面的成员取出来后以逗号分隔

3.yield*

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

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 }

4.数组的遍历

在遍历数组的时候实际上就是调用了iterator接口,所以任何接受数组为参数的场合都默认调用iterator接口,下面几个例子都会调用iterator接口

  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()
  • Promise.all()
  • Promise.race()

5.Set和Map结构的遍历方法

Set和Map结构在调用keys,values,entries方法时会返回一个遍历器对象。(SetIterator和MapIterator)

遍历器对象的其他方法


遍历器除了next方法外,还有return方法和throw方法,如果要自己部署iterator接口,next方法是必要的,但是return方法和throw方法可以不要。

return方法是在循环中途要退出遍历的时候调用。

throw方法是用于配合Generator函数

JavaScript的几种遍历方法


for循环

for (let i=0;i<arr.length;i++)
    console.log(i);

for循环写起来较其他遍历方法会繁琐一点,需要声明一个用来遍历的变量,还需要写遍历结束条件以及步长。

forEach

arr.forEach(i=>console.log(i));

forEach虽然在遍历时比较方便,但不能在中途跳出遍历,break语句和return语句都无效

for...in

for (let i in arr)
    console.log(i);

for..in会遍历数组的键名,但因为键名是字符串,所以只能返回字符串,所以for...in并不适合用于遍历数组,而适合用于遍历对象

for...of

for (let i of arr)
    console.log(i);

for...of有和for...in一样简洁的写法,而且没有for...in的缺点,同时也可以使用break语句和return语句停止遍历。


参考自阮一峰的《ECMAScript6入门》

猜你喜欢

转载自blog.csdn.net/zemprogram/article/details/86413195