迭代与迭代器(Iterable and Iterator)

我们在很前面的时候就讲到了迭代器这么一个东西。那么他究竟是什么呢?又有什么样的作用呢?本节我们就来讲述 Iterables 与 Iterators。也就是可迭代性与迭代器。

概述

ES6 中新增了一个迭代的接口,叫做可迭代性(Iterable )。本节呢,将要向大家讲述它是怎么工作的,以及它运用于那些 ECMAScript 语言类型中。

我们来看看迭代(Iteration )是什么,它分为两个部分:

  • Iterable:可迭代性是一种数据结构,它希望使其元素可以访问公共部分。它通过内置系统的一个方法,键为 Symbol.iterator。这个方法就是迭代器的工厂。
  • Iterator:用于遍历数据结构的元素的指针。

以下的值具有可迭代性:

  • Array
  • String
  • Map
  • Set
  • DOM 数据结构(在程序中工作的那部分)

那么具有可迭代性的对象可以做哪些事呢?如下所示:
(1)解构

let [a,b] = new Set(['a', 'b']);
console.log(a);  // "a"
console.log(b);  // "b"

(2)for-of 循环

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

(3)Array.from( ) 方法

let arr = Array.from(new Set(['a', 'b', 'c']));
//  ["a", "b", "c"]

(4)展开操作符(…)

let arr = [...new Set(['a', 'b', 'c'])]; 
// ["a", "b", "c"]

(5)Map 和 Set 构造函数

let map = new Map([
    [1,'a'],
    [2,'b']
]);

let set = new Set(['a', 'b', 'c']);

(6)Promise.all( ) 、Promise.race( ) 方法

Promise.all(iterableOverPromises).then(···);
Promise.race(iterableOverPromises).then(···); 

(7)yeild(*

yield* anIterable; 

看完以上的这些,记住了,基本上这节内容就大致清楚了。

可迭代性

这里我们将要讲到可迭代性的思想,也就是说,可迭代到底是什么?

可迭代性的思想:

  • 数据消费者(Data consumers ):JavaScript 具有“消费”数据的语言结构。例如,for-of 循环用于遍历值,而扩展运算符(…)将值插入到数组或函数调用中。
  • 数据源(Data Sources):数据消费者可以从各种来源获取值。例如,您可能需要遍历数组的元素,或者Map 中的键值(使用 entries 方法)以及 String 实例中的字符等。

我们这里必须清楚,想要每个数据消费者都能获取数据源,这是不切实际的。尤其是当我们创建新的源时(例如通过一个库)。因此,ES6 就提供了 Iterable 的接口。数据消费者使用它,而数据源负责实现它。我们来看下图:

《迭代与迭代器(Iterable and Iterator)》 迭代关系

这里有两个知识:

  • 源(Source ):如果一个键拥有 Symbol.Iterator 方法并返回迭代器时,它的值就被认为是可迭代的。迭代器是一个对象,它可以使用 next( ) 方法返回值。我们可以这样说:这个方法每次使用时,都可以枚举值。
  • 消耗(Consumption ):数据使用者使用迭代器来检索它们消耗的值。

这里我们来举个之前的例子吧:

let set = new Set(['a','b','c']);
let keys = set.keys();
console.log(keys.next());  // {value: "a", done: false}
console.log(keys.next());  //  {value: "b", done: false}
console.log(keys.next());  // {value: "c", done: false}
console.log(keys.next());  // {value: undefined, done: true}

上述示例中,我们使用了 keys() 方法与 next() 方法来进行该 Set 实例每个值的枚举与输出。就是因为 Set 拥有 Symbol.Iterator 方法。因此我们还可以使用下述的方式进行遍历:

let set = new Set(['a','b','c']);
let iter = set[Symbol.iterator]();
console.log(iter.next());  // {value: "a", done: false}
console.log(iter.next());  //  {value: "b", done: false}
console.log(iter.next());  // {value: "c", done: false}
console.log(iter.next());  // {value: undefined, done: true}

输出结果一致,没有问题。

可以看到,next( ) 返回包装在对象中的每个项目,作为属性值的值。布尔属性 done 指示何时已达到项目序列的结束。

迭代和迭代器是所谓的协议(方法加上使用它们的规则)的一部分迭代。此协议的关键特性是它是顺序的:迭代器一次返回一个值。 这意味着如果一个可迭代的数据结构是非线性的(如一颗树),迭代将会使其线性化。

迭代的数据源

在概述中我举了几个例子。以下我使用 for-of 循环来迭代每个类型。我们一起来看下输出的值是什么:

(1)Array

for (let x of ['a', 'b']) {
    console.log(x);
} 
// "a"
// "b"

**(2)String **

for (let x of 'Hello') {
    console.log(x);
}
// "H"
// "e"
// "l"
// "l"
// "e" 

**(3)Map **

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

**(4)Set **

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

(5)arguments

function printArgs() {
    for (let x of arguments) {
        console.log(x);
    }
}
printArgs('a', 'b'); 
// "a"
// "b"

(6)DOM 数据结构

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

现在更加清晰了吧?举出这么多例子的目的就是希望大家熟悉它。

迭代计算的数据

不是所有可迭代内容都必须来自数据结构,它也可以即时计算。

例如我们前面学习的 Array、Map、Set 都拥有 entries( )、keys( )、values( ) 三个方法。它们都是即时计算实例中的内容,再进行输出。

  • entries( ) 方法返回实例的 [key, value] 的数组。
  • keys( ) 方法返回实例的键。
  • values( ) 方法返回实例的值。

但是,我们需要知道,Object 类型是没有迭代性可言的。因此它没有 for-of 循环,只有 for-in 的遍历。当然,在未来可能会有内置方法。

// 错误
for (let x of {a: 1, b: 2}) { // TypeError
    console.log(x);
}

总结

本节把迭代的知识点讲完了,但是具体的用法还是比较多的。需要我们不断地实践。当你忘记的时候,就回来看看,将这些可迭代的对象牢记在脑海中,在使用时,就不会出错。

    原文作者:Hushaby丶
    原文地址: https://www.jianshu.com/p/2d0187f30a54
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞