写点什么

ES6 中的新特性:Iterables 和 iterators

发布于: 2021 年 03 月 21 日

简介

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

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


发布于: 2021 年 03 月 21 日阅读数: 7
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
ES6中的新特性:Iterables和iterators