简介

为了不便汇合数据的遍历,在ES6中引入了一个iteration的概念。为咱们提供了更加不便的数据遍历的伎俩。

一起来学习一下吧。

什么是iteration

iteration也称为遍历,就是像数据库的游标一样,一步一步的遍历汇合或者对象的数据。

依据ES6的定义,iteration次要由三局部组成:

  1. Iterable

先看下Iterable的定义:

interface Iterable {    [Symbol.iterator]() : Iterator;}

Iterable示意这个对象外面有可遍历的数据,并且须要实现一个能够生成Iterator的工厂办法。

  1. Iterator
interface Iterator {    next() : IteratorResult;}

能够从Iterable中构建Iterator。Iterator是一个相似游标的概念,能够通过next拜访到IteratorResult。

  1. IteratorResult

IteratorResult是每次调用next办法失去的数据。

interface IteratorResult {    value: any;    done: boolean;}

IteratorResult中除了有一个value值示意要获取到的数据之外,还有一个done,示意是否遍历实现。

Iterable是一个接口,通过这个接口,咱们能够连贯数据提供者和数据消费者。

Iterable对象叫做数据提供者。对于数据消费者来说,除了能够调用next办法来获取数据之外,还能够应用for-of 或者 ...扩大运算符来进行遍历。

for-of的例子:

  for (const x of ['a', 'b', 'c']) {      console.log(x);  }

...扩大运算符的例子:

 const arr = [...new Set(['a', 'b', 'c'])];

Iterable对象

ES6中,能够被称为Iterable对象的有上面几种:

  • Arrays
  • Strings
  • Maps
  • Sets
  • DOM

先看一个Arrays的状况,如果咱们有一个Arrays,能够通过Symbol.iterator这个key来获取到Iterator:

> const arr = ['a', 'b', 'c'];> const 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 }

更加简略的方法就是应用for-of:

for (const x of ['a', 'b']) {    console.log(x);}// Output:// 'a'// 'b'

看一个遍历String的状况,String的遍历是通过Unicode code points来辨别的:

for (const x of 'a\uD83D\uDC0A') {    console.log(x);}// Output:// 'a'// '\uD83D\uDC0A' (crocodile emoji)

下面的例子中,根底类型的String在遍历的时候,会主动转换成为String对象。

Maps是通过遍历entries来实现的:

const map = new Map().set('a', 1).set('b', 2);for (const pair of map) {    console.log(pair);}// Output:// ['a', 1]// ['b', 2]

还记得之前提到的WeakMaps吗?

WeakMap,WeakSet和Map于Set的区别在于,WeakMap的key只能是Object对象,不能是根本类型。

为什么会有WeakMap呢?

对于JS中的Map来说,通常须要保护两个数组,第一个数组中存储key,第二个数组中存储value。每次增加和删除item的时候,都须要同时操作两个数组。

这种实现有两个毛病,第一个毛病是每次查找的时候都须要遍历key的数组,而后找到对应的index,再通过index来从第二个数组中查找value。

第二个毛病就是key和value是强绑定的,即便key不再被应用了,也不会被垃圾回收。

所以引入了WeakMap的概念,在WeakMap中,key和value没有这样的强绑定关系,key如果不再被应用的话,能够被垃圾回收器回收。

因为援用关系是weak的,所以weakMap不反对key的遍历,如果你想遍历key的话,请应用Map。

看下Set的遍历:

const set = new Set().add('a').add('b');for (const x of set) {    console.log(x);}// Output:// 'a'// 'b'

咱们还能够遍历arguments对象:

function printArgs() {    for (const x of arguments) {        console.log(x);    }}printArgs('a', 'b');// Output:// 'a'// 'b'

对于大部分DOM来说,也是能够遍历的:

for (const node of document.querySelectorAll('div')) {    ···}

一般对象不是可遍历的

简略对象就是通过字面量创立进去的对象,这些对象尽管也有key-value的内容,然而是不可遍历的。

为什么呢?

因为可遍历对象比方Array,Map,Set也是一般对象的一种特例。如果一般对象能够遍历了,那么会导致能够遍历对象的一些遍历中的抵触。

for (const x of {}) { // TypeError    console.log(x);}

尽管不能间接遍历一般对象,然而咱们能够通过应用objectEntries办法来遍历一般对象。

先看下objectEntries的实现:

function objectEntries(obj) {    let iter = Reflect.ownKeys(obj)[Symbol.iterator]();    return {        [Symbol.iterator]() {            return this;        },        next() {            let { done, value: key } = iter.next();            if (done) {                return { done: true };            }            return { value: [key, obj[key]] };        }    };}

咱们通过Reflect.ownKeys()反射拿到对象中的iterator.而后通过这个iterator来进行一般对象的遍历。

看下具体的应用:

const obj = { first: 'Jane', last: 'Doe' };for (const [key,value] of objectEntries(obj)) {    console.log(`${key}: ${value}`);}// Output:// first: Jane// last: Doe

自定义iterables

除了ES6中默认的iterables之外,咱们还能够自定义iterables。

因为iterables是一个接口,咱们只须要实现它就能够了。咱们看一个iterables的例子:

function iterateOver(...args) {    let index = 0;    const iterable = {        [Symbol.iterator]() {            const iterator = {                next() {                    if (index < args.length) {                        return { value: args[index++] };                    } else {                        return { done: true };                    }                }            };            return iterator;        }    }    return iterable;}

iterateOver办法会返回一个iterable对象。在这个对象中,咱们实现了Symbol.iterator为key的办法。这个办法返回一个iterator,在iterator中,咱们实现了next办法。

下面的办法应用起来是上面的成果:

// Using `iterateOver()`:for (const x of iterateOver('fee', 'fi', 'fo', 'fum')) {    console.log(x);}// Output:// fee// fi// fo// fum

下面的例子中,如果Symbol.iterator返回的对象是iterable自身,那么iterable也是一个iterator。

function iterateOver(...args) {    let index = 0;    const iterable = {        [Symbol.iterator]() {            return this;        },        next() {            if (index < args.length) {                return { value: args[index++] };            } else {                return { done: true };            }        },    };    return iterable;}

这样做的益处就是,咱们能够应用for-of同时遍历iterables和iterators,如下所示:

const arr = ['a', 'b'];const iterator = arr[Symbol.iterator]();for (const x of iterator) {    console.log(x); // a    break;}// Continue with same iterator:for (const x of iterator) {    console.log(x); // b}

敞开iterators

如果咱们须要遍历的过程中,从iterators中返回该怎么解决呢?

通过实现return办法,咱们能够在程序中断的时候(break,return,throw)调用iterators的return。

function createIterable() {    let done = false;    const iterable = {        [Symbol.iterator]() {            return this;        },        next() {            if (!done) {                done = true;                return { done: false, value: 'a' };            } else {                return { done: true, value: undefined };            }        },        return() {            console.log('return() was called!');        },    };    return iterable;}for (const x of createIterable()) {    console.log(x);    break;}// Output:// a// return() was called!

下面例子中,咱们通过break来中断遍历,最终导致return办法的调用。

留神,return办法必须要返回一个对象,{ done: true, value: x }

总结

下面就是ES6中引入的Iterables和iterators的一些概念。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/es6-iterables-iterator/

本文起源:flydean的博客

欢送关注我的公众号:「程序那些事」最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!