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的对象。

这里可能会有提出输出最后一个值时,使done变为true结束的想法,因为这样就不会多输出一个{value:undefined, done:true}了,但我们可以通过下面的for...of循环的等价for形式来理解为什么输出最后一个值时done还要为false。

for(var v,res;(res=it.next())&&!res.done;){ // it为迭代器
    v=res.value;
    console.log(v);
}

如果输出最后一个值时done为true,那么该值会被抛弃,不会进入循环体中,所以应该在输出最后一个值后再把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: true}

var str='hello';
var i=str[Symbol.iterator]();
i.next();//{value: "h", done: false}
i.next();//{value: "e", done: false}
i.next();//{value: "l", done: false}
i.next();//{value: "l", done: false}
i.next();//{value: "o", done: false}
i.next();//{value: undefined, done: true}

这里的'hello'实际上被强制转换成String对象封装对象,通过获取其迭代器遍历其字符。

部署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语句停止遍历。实际上for...of是在迭代中自动调用Symbol.iterator函数来构建一个迭代器,并自动调用next(),且不向next()传入任何值,在接收到done:true之后停止。

for...of可遍历的类型有:数组,Map,Set,Generator,字符串,类数组如参数对象arguments

自定义迭代器例子

(以下例子出自《你不知道的JavaScript 下卷》

无限斐波拉契序列

var Fib = {
    [Symbol.iterator]() {
        var n1 = 1,
            n2 = 1; // 初始数字,即序列的1,2位

        return {
            //使迭代器变为iterable,通过返回this
            [Symbol.iterator]() { return this; },

            next() {
                var current = n2;
                n2 = n1;
                n1 = n1 + current; // 相加得到下一个数,再依次传递给n2保存,再下一次循环通过current返回
                return { value: current, done: false };
            },

            return (v) {
                console.log("Fibonacci sequence abandoned");
                return { value: v, done: true }; // done变为true,结束迭代
            }
        }
    }
}

for (var v of Fib) {
    console.log(v);
    if (v > 100) break;
}


// 1 1 2 3 5 8 13 21 34 55 89 144
// Fibonacci sequence abandoned

如果没有使用break跳出循环,这个循环会一直持续下去。

执行函数数组中的函数

var tasks = {
    [Symbol.iterator]() {
        var steps = this.actions.slice(); // 获取actions中的方法名数组

        return {
            // 使迭代器称为iterator
            [Symbol.iterator]() { return this; },

            next(...args) {
                if (steps.length > 0) {
                    let res = steps.shift(...args); // 使用next传递过来的参数执行函数
                    return { value: res, done: false };
                } else {
                    return { done: true };
                }
            },

            return (v) {
                steps.length = 0;
                return { value: v, done: true };
            }
        }
    },
    actions: [] // 存储方法的数组
}

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

        Kyle Simpson的《你不知道的JavaScript 中卷》《你不知道的JavaScript 下卷》


ES6学习笔记目录(持续更新中)

 

ES6学习笔记(一)let和const

ES6学习笔记(二)参数的默认值和rest参数

ES6学习笔记(三)箭头函数简化数组函数的使用

ES6学习笔记(四)解构赋值

ES6学习笔记(五)Set结构和Map结构

ES6学习笔记(六)Iterator接口

ES6学习笔记(七)数组的扩展

ES6学习笔记(八)字符串的扩展

ES6学习笔记(九)数值的扩展

ES6学习笔记(十)对象的扩展

ES6学习笔记(十一)Symbol

ES6学习笔记(十二)Proxy代理

ES6学习笔记(十三)Reflect对象

ES6学习笔记(十四)Promise对象

ES6学习笔记(十五)Generator函数

ES6学习笔记(十六)asnyc函数

ES6学习笔记(十七)类class

ES6学习笔记(十八)尾调用优化

ES6学习笔记(十九)正则的扩展

ES6学习笔记(二十)ES Module的语法

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值