迭代器与生成器

目录

迭代器

什么是迭代器模式

可迭代协议

迭代器协议

自定义迭代器

 提前终止迭代器

 生成器

生成器函数的定义

生成器的调用与执行

yield关键字中断执行

.使用yield实现输入和输出

使用yield*可以一次性迭代可迭代对象

提前终止生成器

return()

throw()


迭代器

  1. 什么是迭代?

    答:按照顺序反复多次执行一段程序,通常有明确的终止条件

  2. JavaScript为什么需要像Python、Java、C++新增了迭代器模式

    答:需要事先知道如何使用数据结构,遍历顺序并不是数据结构固有的(比如对象的属性是无序的)

迭代器是通用的方法,不需要知道迭代对象的数据结构,只需要能按照一定顺序取得数据即可

什么是迭代器模式

特定结构的可迭代对象,对外暴露了Iterable(可迭代对象)接口,而且可以通过Iterator(迭代器)消费

可迭代对象是一种抽象的说法。基本上,可以把可迭代对象理解成数组或集合这样的集合类型的对象。它们包含的元素都是有限的,而且都具有无歧义的遍历顺序:

// 数组的元素是有限的
// 递增索引可以按序访问每个元素
let arr = [3, 1, 4]; 
// 集合的元素是有限的
// 可以按插入顺序访问每个元素
let set = new Set().add(3).add(1).add(4); 

不过,可迭代对象不一定是集合对象,也可以是仅仅具有类似数组行为的其他数据结构,比如本章开头提到的计数循环。该循环中生成的值是暂时性的,但循环本身是在执行迭代。计数循环和数组都具有可迭代对象的行为。
任何实现 Iterable 接口的数据结构都可以被实现 Iterator 接口的结构“消费”(consume)。迭代器(iterator)是按需创建的一次性对象。每个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联可迭代对象的 API。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。这种概念上的分离正是 Iterable 和 Iterator 的强大之处。
 

可迭代协议

ECMA规定,暴露一个属性作为迭代器,这个属性必须使用特殊的Symbol.iterator作为key。

以下内置类型都实现了Iterable接口,1.字符串、2.数组、3.映射、4.集合、5.arguments对象、6.NodeList等Dom集合类型

以下代码仅演示和理解迭代器,实际开发不需要这样操作。

let num = 1; 
let obj = {}; 
// 这两种类型没有实现迭代器工厂函数
console.log(num[Symbol.iterator]); // undefined 
console.log(obj[Symbol.iterator]); // undefined 
let str = 'abc'; 
let arr = ['a', 'b', 'c']; 
let map = new Map().set('a', 1).set('b', 2).set('c', 3); 
let set = new Set().add('a').add('b').add('c'); 
let els = document.querySelectorAll('div'); 
// 这些类型都实现了迭代器工厂函数
console.log(str[Symbol.iterator]); // f values() { [native code] } 
console.log(arr[Symbol.iterator]); // f values() { [native code] } 
console.log(map[Symbol.iterator]); // f values() { [native code] } 
console.log(set[Symbol.iterator]); // f values() { [native code] } 
console.log(els[Symbol.iterator]); // f values() { [native code] } 
// 调用这个工厂函数会生成一个迭代器
console.log(str[Symbol.iterator]()); // StringIterator {} 
console.log(arr[Symbol.iterator]()); // ArrayIterator {} 
console.log(map[Symbol.iterator]()); // MapIterator {} 
console.log(set[Symbol.iterator]()); // SetIterator {} 
console.log(els[Symbol.iterator]()); // ArrayIterator {}

以下方法可以直接使用上面这些可迭代对象,1.for-of、2.数组解构、3.扩展运算符、4.Array.from()、5.创建集合、6.创建集合和映射、7.Promise.all()和Promise.race()、8.在generator生成器中使用yield*操作符、

let arr = ['a','b','c']
// for-of循环
for (const el of arr) { console.log(el) } // a  b  c
// 数组解构
let [x,y,z] = arr
console.log(x,y,z);     // a b c
// 扩展运算符
let arr2 = [...arr]
console.log(arr2);      // ['a', 'b', 'c']
// Array.from()
let arr3 = Array.from(arr)
console.log(arr3);      // ['a', 'b', 'c']
// 集合和映射
let set = new Set(arr)
console.log(set);       // Set(3) {'a', 'b', 'c'}
let pairs = arr.map((x,i) => [x,i])
let map = new Map(pairs)
console.log(map);       // Map(3) {'a' => 0, 'b' => 1, 'c' => 2}

迭代器协议

迭代器是一种一次性使用的对象。迭代器API使用next()方法,返回一个IterableResult对象,包含两个属性键名为done和value。

每个迭代器都表示对可迭代对象的一次性有序遍历。不同迭代器的实例相互之间没有联系,只会独
立地遍历可迭代对象: 

    let arr = ['a', 'b']                     // 可迭代对象
    console.log(arr[Symbol.iterator]);      // 迭代器工厂函数,ƒ values() { [native code] }
    let iterFirst = arr[Symbol.iterator]()      // 调用工厂函数生成的第一个迭代器
    let iterSecond = arr[Symbol.iterator]()      // 生成的第二个迭代器

    console.log(iterFirst.next());      // {value: 'a', done: false}
    console.log(iterSecond.next());     // {value: 'a', done: false}

    console.log(iterFirst.next());      // {value: 'b', done: false}
    console.log(iterSecond.next());     // {value: 'b', done: false}

    console.log(iterSecond.next());     // {value: undefined, done: true}
    console.log(iterSecond.next());     // {value: undefined, done: true}
    console.log(iterSecond.next());     // {value: undefined, done: true}

 迭代器并不与可迭代对象某个时刻的快照绑定,而仅仅是使用游标来记录遍历可迭代对象的历程。 如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应的变化:

    let arr = ['foo', 'baz'];
    let iter = arr[Symbol.iterator]();
    console.log(iter.next()); // { done: false, value: 'foo' } 
    // 在数组中间插入值
    arr.splice(1, 0, 'bar');
    console.log(iter.next()); // { done: false, value: 'bar' } 
    console.log(iter.next()); // { done: false, value: 'baz' } 
    console.log(iter.next()); // { done: true, value: undefined }
  • 不同迭代器之间不会相互影响,可以独立的迭代对象
  • done为一个布尔值只要done为false,则可以继续调用next()方法。value为迭代器返回的当前值。
  • 当done为true时,后续调用next()value为undefiend

自定义迭代器

class Counter {
    constructor(limit) {
        this.limit = limit
    }

    [Symbol.iterator]() {   // 定义[Symbol.iterator]方法,
        let count = 1,      // 这里通过闭包达到一个效果,即使同时创建多个迭代器时也保证计算器count从1 开始(闭包作用之一,避免全局变量count被污染)
            limit = this.limit
        return {
            next() {        // 对迭代器使用next()方法
                if (count <= limit) {
                    return { done: false, value: count++ }
                } else {
                    return { done: true, value: undefined }
                }
            }
        }
    }
}

let counter = new Counter(5)
for (const el of counter) { console.log(el); }      // 1 2 3 4 5

 提前终止迭代器

ES5中提出的forEach是没有终止的(除了trycatch),return()方法可以提前终止迭代。

  1. for-of循环通过break,continue,return或throw提前退出
  2. 解构操作并未消费所有值

 生成器

  1. 什么是生成器?

    生成器本质是函数,在函数名称前加一个星号(*)表示为生成器,星号不受两侧空格的影响。调用之后返回一个生成器对象。

  2. 生成器有什么用?

    生成器是ES6新增的结构,拥有在一个函数块内暂停和恢复代码执行的能力。生成器可以自定义迭代器。

生成器函数的定义

<script>
    function* generatorFn() { }
    let generatorFn = function* () { }
    let foo = {
        * generatorFn() { }
    }
    class Foo {
        * generatorFn() { }
    }
    class Bar {
        static * generatorFn() { }
    }

生成器的调用与执行

    function* generatorFn() {
        console.log('foo');
        return 'foo'
    };
    const result = generatorFn();
    console.log(result);            // generatorFn {<suspended>},调用生成器函数产生一个生成器对象(暂停执行),直接调用生成器函数并不会执行内部代码
    console.log(result.next);       // ƒ next() { [native code] },和迭代器一样有next方法
    console.log(result.next());     // 控制台打印foo  {value: 'foo', done: true},生成器函数只会在初次调用next()方法开始执行,执行内部代码,返回结果类似迭代器
    console.log(result.next());     // {value: undefined, done: true},

yield关键字中断执行

yield关键字可以控制生成器的停止和开始执行。遇到这个关键字,执行停止。停止后只能通过next()方法恢复执行。

    function* generatorFn() {
        console.log('a');
        yield 'foo'
        console.log('b');
        yield 'bar'
        console.log('c');
        return 'baz'
        console.log('d');
    };
    const result = generatorFn();
    console.log(result.next());     // a {value: 'foo', done: false}  遇到yield关键字退出的生成器函数会处在done:false状态。
    console.log(result.next());     // b {value: 'bar', done: false}
    console.log(result.next());     // c {value: 'baz', done: true} 遇到return关键字退出的生成器函数会处在done:true状态。
    console.log(result.next());     //   {value: undefined, done: true} done的状态已经转变为true,所以字符串d并不会打印

 

ield关键字使用的注意点

  1. 和迭代器一样,调用同一个生成器函数生成多个生成器对象,各个对象之间不会影响
  2. yield关键字只允许直接在生成器函数内部使用,并且不允许嵌套层级。否在会无效

直接显式的调用next()的方法用处不大,如果把生成器对象当成可迭代对象,使用起来会更方便。

    function* generatorFn() {
        yield 1
        yield 2
        yield 3
    }

    for (const iterator of generatorFn()) {
        console.log(iterator);      // 1 2 3
    }

.使用yield实现输入和输出

生成器传递参数

    function* generatorFn(init) {
        console.log(init);
        console.log(yield);
        console.log(yield);
    }

    let result = generatorFn('foo')

    result.next('bar')  // foo  第一次调用next传入的值不会被使用,因为这次调用是为了开始执行生成器函数
    result.next('baz')  // baz
    result.next('qux')  // qux
   // 迭代索引
    function* nTimes(n) {
        for (let i = 0; i < n; i++) {
            yield i
        }
    }

    for (const iterator of nTimes(3)) {
        console.log(iterator);      // 0 1 2
    }


    // 实现范围输出
    function* range(start, end) {
        while (end > start) {
            yield start++
        }
    }

    for (const iterator of range(4, 8)) {
        console.log(iterator);      // 4 5 6 7
    }

    // 实现填充数组
    function* fill(n) {
        while (n--) {
            yield 0
        }
    }

    console.log(Array.from(fill(8)));   // [0, 0, 0, 0, 0, 0, 0, 0]

使用yield*可以一次性迭代可迭代对象

yield* 本质是语法糖,yield*本质上将一个可迭代对象序列化为一连串可以单独产出的值,所以等同于把yield放入循环。

    // 这种写法可以直接用 yield* 来代替
    // function* generatorFn() {
    //     for (const iterator of [1, 2, 3]) {
    //         yield iterator
    //     }
    // }

    function* generatorFn() {
        yield* [1, 2, 3]
        yield* [4, 5]
        yield* [6, 7]
    }

    let result = generatorFn()

    for (const iterator of generatorFn()) {
        console.log(iterator);  // 1,2,3,4,5,6,7
    }

yield*关联迭代器返回done:true的value属性,对普通迭代器这个值为undefined。而对于生成器函数来说,这个值就是生成器函数返回的值。

   function* generatorFn() {
        console.log('iter value', yield* [1, 2, 3]);  // yield* 关联普通迭代器
    }

    for (const iterator of generatorFn()) {
        console.log('value', iterator);
    }
    /* 
        value 1
        value 2
        value 3
        iter value undefined
    */

    function* innerGeneratorFn() {
        yield 'foo'
        return 'bar'
    }

    function* outerGeneratorFn() {
        console.log('iter value:', yield* innerGeneratorFn());  // yield* 关联生成器函数
    }

    for (const iterator of outerGeneratorFn()) {
        console.log('value', iterator);
    }
/* 
            value foo
            iter value: bar
        */

提前终止生成器

与迭代器类似,生成器也支持可关闭的概念。一个实现Iterator接口的对象一定有next()方法,还有一个可选的return()方法用于提前终止迭代器。除了以上方法还有throw()

return()

return()方法会强制生成器进入关闭状态。return 传入的值就是终止迭代器对象的值

function* generatorFn() {
        yield* [1, 2, 3]
    }

    const result = generatorFn()

    console.log(result);                // generatorFn {<suspended>}
    // console.log(result.return(4));      // {value: 4, done: true}
    console.log(result);                // generatorFn {<closed>}
    console.log(result.next());         // {value: undefined, done: true}
    console.log(result.next());         // {value: undefined, done: true}
    console.log(result.next());         // {value: undefined, done: true}

throw()

throw()方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未被处理,生成器就会关闭。

    const result = generatorFn()

    console.log(result);    // generatorFn {<suspended>}
    try {
        result.throw('error')
    } catch (error) {
        console.log(error);     // error
    }
    console.log(result);        // generatorFn {<closed>}

不过,假如生成器函数内部处理了这个错误,那么生成器就不会关闭,而且还可以恢复执行。错误处理会跳过对应的yield。

    function* generatorFn() {
        yield* [1, 2, 3]
    }

    function* generatorFn() {
        for (const iterator of [1, 2, 3]) {
            try {
                yield iterator
            } catch (error) { }
        }
    }

    const result = generatorFn()

    console.log(result.next());     // {value: 1, done: false}
    result.throw('foo')
    console.log(result.next());     // {value: 3, done: false}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值