ES6中的新特性:Iterables和iterators

简介

为了方便集合数据的遍历,在ES6中引入了一个iteration的概念。为咱们提供了更加方便的数据遍历的手段。node

一块儿来学习一下吧。es6

什么是iteration

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

根据ES6的定义,iteration主要由三部分组成:数组

  1. Iterable

先看下Iterable的定义:学习

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

Iterable表示这个对象里面有可遍历的数据,而且须要实现一个能够生成Iterator的工厂方法。this

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

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

  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的博客

欢迎关注个人公众号:「程序那些事」最通俗的解读,最深入的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

相关文章
相关标签/搜索