乐趣区

关于ecmascript:ES6中的新特性Iterables和iterators

简介

为了不便汇合数据的遍历,在 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 的博客

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

退出移动版