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的语法

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值