迭代器和生成器总结

本文详细介绍了JavaScript中的迭代器、可迭代对象的概念,包括它们的实现方式、next方法的使用以及应用场景,如for...of、生成器函数、Promise处理等。同时涵盖了生成器的特性、中断机制和与async/await的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

迭代器(iterator)

什么是迭代器: 

迭代器是一个对象,可以对另外一个对象进行遍历,称之为迭代器,其类似于光标,使用的时候不需要关心其内部的实现方法。

可迭代协议

一个迭代器需要满足可迭代协议,其内容包括

        1. 必须包含一个next方法

        2. next方法的返回值需要包含

                (1) value: 迭代器返回的值,在done为false时有效

                (2) done: 迭代器是否迭代完成,默认可省略,当迭代器迭代结束时,为true

迭代器对象的使用

 通过调用其next方法可以逐步执行遍历,next方法可以传递进一个参数或者不传入参数

e.g. 对一个数组实现迭代器:

        const names = ['abc', 'def', 'ghi', 'xxx', 21312312, { test: 1 }]
        // 创建迭代器
        const namesIterator = {
            index: 0,
            next: function () {
                if (this.index < names.length) {
                    return {
                        value: names[this.index++],
                        done: false,
                    }
                } else {
                    return { done: true }
                }
            }
        }
        console.log(namesIterator.next())
        console.log(namesIterator.next())
        console.log(namesIterator.next())
        console.log(namesIterator.next())
        console.log(namesIterator.next())
        console.log(namesIterator.next())
        console.log(namesIterator.next())

通过执行.next() 方法可以执行迭代,观察 done和value的关系

e.g. 优化一下,我们可以写一个工厂方法,传递进去一个数组,返回对应的迭代器对象

        const names = ['abc', 'def', 'ghi', 'xxx', 21312312, { test: 1 }]
        // 迭代器工厂
        function createArrayIterator(arr) {
            let index = 0;
            return {
                next: function () {
                    if (index < arr.length) {
                        return {
                            name: arr[index++],
                            done: false,
                        }
                    } else {
                        return { done: true }
                    }
                }
            }
        }
        // 创建迭代器
        const namesIterator = createArrayIterator(names)

        console.log(namesIterator.next())
        console.log(namesIterator.next())
        console.log(namesIterator.next())
        console.log(namesIterator.next())
        console.log(namesIterator.next())
        console.log(namesIterator.next())
        console.log(namesIterator.next())
可迭代对象

可迭代对象和迭代器是不同的概念,当一个对象实现了[Symbol.iterator]方法,这个对象就是可迭代对象,注意 [Symbol.iterator]需要实现为一个工厂方法,其返回值需要是一个迭代器。

        const infos = {
            data: [
                'iterator',
                'generator',
                'test',
                100,
                { test: 100 }
                [1, 2, 3]
            ],
            [Symbol.iterator]: function () {
                let index = 0
                return {
                    next: () => {
                        if (index < this.data.length) {
                            return {
                                value: this.data[index++],
                                done: false,
                            }
                        } else {
                            return { done: true, }
                        }
                    }
                }
            }
        }

infos对象实现了 [Symbol.iterator] 成为一个可迭代对象,[Symbol.iterator]对应一个工厂方法,其返回一个迭代器,用来遍历infos中的data属性

请注意这里next方法的this指向问题,如果使用function(){} 其内部的this指向迭代器本身(因为调用的时候一定是 迭代器.next() )所以需要使用箭头函数,让this继承工厂方法的this,而工厂方法是由infos对象[Symbol.iterator]调用创建,所以this指向infos。

可迭代对象的应用场景

1. Javascipt语法: for...of , 展开语法,yield* 委托,解构赋值

2. 一些对象的构造方法传入的参数是可迭代对象 如: new Map(iterable). new Set(iterable)

3. 一些方法调用 如: Promise.all(iterable) Promise.race(iterable) Array.from(iterable)

我们经常使用 Array.from 将伪数组arguments转换成数组,arguments也是个可迭代对象,所以可以直接传入Array.from(iterable)中

原生可迭代对象  

String Array Map Set 伪数组: arguments Nodelist 

注意: 普通对象Object 不是可迭代对象,对一个对象 for of 或者使用展开运算符在非赋值的情况下展开对象会报错 如 foo(...obj) 

用在赋值中的展开对象是特殊处理,和可迭代无关,如:

{

        a:100,

        ...obj

}

自定义类迭代器

想创建一个对象就默认带iterator,可以将迭代器工厂放到其原型链上(作为类方法)

类中没有function关键字,可直接写成[Symbol.iterabor](){}的方式

        class Person {
            constructor(name, age, height, friends) {
                this.name = name
                this.age = age
                this.height = height
                this.friends = friends
            }
               // 定义类迭代器工厂方法,注意是放在原型中的

            [Symbol.iterator]() {
                const entries = Object.entries(this)
                let index = 0
                return {
                    next() {
                        if (index < entries?.length) {
                            return {
                                value: entries[index++],
                                done: false,
                            }
                        } else {
                            return {
                                done: true
                            }
                        }
                    }
                }
            }
        }

        const p1 = new Person('TOM', 18, 1.80, ['jack', 'lucy', 'jasmine'])
        const p2 = new Person('Jack', 17, 1.90, ['tom', 'lucy', 'jasmine'])

        for(let p of p1){
            const [key,value] = p 
            console.log(key,value)
        }

        for(let p of p2){
            const [key,value] = p 
            console.log(key,value)
        }
迭代器中断

迭代器在以下情况下会中断 

1. 循环遍历时,使用break return等中断循环

2. 解构赋值时没有结构所有的值

可以通过在迭代器中设置return方法作为中断的回调,return方法也需要满足可迭代协议 返回(done: true)

对上述例子做修改如下:

 class Person {
            constructor(name, age, height, friends) {
                this.name = name
                this.age = age
                this.height = height
                this.friends = friends
            }

            [Symbol.iterator]() {
                const entries = Object.entries(this)
                let index = 0
                return {
                    next() {
                        if (index < entries?.length) {
                            return {
                                value: entries[index++],
                                done: false,
                            }
                        } else {
                            return {
                                done: true
                            }
                        }
                    },
                    // 设置迭代器中断的回调
                    return(){
                        console.log('iterator中断')
                        return {done:true}
                    }
                }
            }
        }

        const p1 = new Person('TOM', 18, 1.80, ['jack', 'lucy', 'jasmine'])
        const p2 = new Person('Jack', 17, 1.90, ['tom', 'lucy', 'jasmine'])

        for(let p of p1){
            const [key,value] = p 
            console.log(key,value)
        }

        for(let p of p2){
            const [key,value] = p 
            if(value === 1.90){
                break
            }
            console.log(key,value)
        }

在循环中强制中断迭代,会触发return函数,输出"iterator中断"并且返回 { done: true }

生成器 (generator)

ES6中新增特性,是特殊的迭代器,是一种函数控制和使用的方案,可以灵活的控制函数的暂停和执行

生成器函数
  1. 声明的时候需要在function后加* function*
  2. 需要通过yield函数来控制函数的执行和暂停
  3. 生成器函数调用后返回一个生成器Generator 生成器本身是特殊的迭代器 
  4. 生成器的执行需要对生成器对象Generator调用next()方法
        function* foo(){
            yield console.log("step1")
            yield console.log("step2")
            yield console.log("step3")
        }

        // 生成器对象
        const fooGenerator = foo()

        console.log(fooGenerator.next())
        console.log(fooGenerator.next())
        console.log(fooGenerator.next())

通过生成器对象,我们可以一步一步的执行生成器函数,每次调用.next 会在对应的yield标记处停下,并且处理参数传递和返回值。 

请区分 生成器函数 和 生成器

生成器函数是用 function* 声明 并且用yield标记暂停的函数

生成器是生成器函数调用返回的特殊的迭代器 通过生成器.next可以逐步执行生成器函数

生成器函数的调用并不立刻执行函数

参数和返回值

1. Next函数的返回值为下一个yield右边表达式返回的值

2. Next函数可以传递参数,作为当前yield左边LHS操作的赋值 (结合上面说的,迭代器协议 next函数可以不传递参数或者传递一个参数)

3. next第一次传递的参数没用作用 会被忽略,一般情况下通过生成器函数的参数来传递第一步所需的值

举例说明: 

        function* foo() {
            console.log('init')
            const p1 = yield 'return 1'
            console.log("step1", p1)
            const p2 = yield 'return 2'
            console.log("step2", p2)
            const p3 = yield 'return 3'
            console.log("step3", p3)
            return 'end'
        }

        // 生成器对象
        const fooGenerator = foo()

        console.log(fooGenerator.next(1))
        console.log(fooGenerator.next(2))
        console.log(fooGenerator.next(3))
        console.log(fooGenerator.next(4))

运行过程如下:  

结果如下:

 

生成器终止

生成器是特殊的迭代器,除了next方法, 还实现了return方法和throw方法

使用生成器.return() 方法可以提前结束生成器函数的运行

使用生成器.throw(err message) 可以向生成器函数内部抛出一个异常,同样可以提前结束生成器函数的运行,但是需要注意用throw的时候需要用try catch处理异常

用生成器代替迭代器

使用生成器函数可以自动生成一个特殊的迭代器(生成器)借此我们可以简化迭代器 

e.g. 数组迭代器,使用生成器函数生成的迭代器,在每次运行next的时候都会运行奥yield标志处暂停,并且返回yield后的值,直到下一次next运行,使用生成器的方式可以更好的“生成”迭代器

        const names = ['abc', 'def', 'ghi', 'xxx', 21312312, { test: 1 }]
        // 迭代器工厂
        function* createArrayGenerator(arr) {
            for(let i=0;i<arr.length;i++){
                yield arr[i]
            }
        }

        const arrayGenerator = createArrayGenerator(names)
        console.log(arrayGenerator.next()?.value)
        console.log(arrayGenerator.next()?.value)
        console.log(arrayGenerator.next()?.value)

e.g. 如果要写在类中,可以直接用 *[Symbol.iterator](){}的形式:

        class Person {
            constructor(name, age, height, friends) {
                this.name = name
                this.age = age
                this.height = height
                this.friends = friends
            }

            *[Symbol.iterator]() {
                const entries = Object.entries(this)
                for (let e of entries) {
                    yield e
                }
            }
        }

        const p1 = new Person('TOM', 18, 1.80, ['jack', 'lucy', 'jasmine'])
        const p2 = new Person('Jack', 17, 1.90, ['tom', 'lucy', 'jasmine'])

        for (let p of p1) {
            const [key, value] = p
            console.log(key, value)
        }

        for (let p of p2) {
            const [key, value] = p
            console.log(key, value)
        }

e.g.范围生成器 一种惰性求值

        function* createRangeGenerator(start,end){
            for(let i = start;i<end;i++){
                yield i
            }
        }
        const gen = createRangeGenerator(1,10)
        console.log(gen.next())
惰性求值:迭代器是一种惰性求值的机制,只有在需要下一个元素时才会取值,这样可以节省内存,提高性能

yield委托

yield* 后面根一个可迭代对象,其可以传递给next一个可迭代对象,也就是yield会遍历每一个元素,并且返回

使用yield委托,可以更简化上面的生成器

        const names = ['abc', 'def', 'ghi', 'xxx', 21312312, { test: 1 }]
        // yield*
        function* createArrayGenerator(arr) {
            console.log(yield* arr)  //yield* 返回undefined
        }
       class Person {
            constructor(name, age, height, friends) {
                this.name = name
                this.age = age
                this.height = height
                this.friends = friends
            }

            *[Symbol.iterator]() {
                yield* Object.entries(this)
            }
        }

 

生成器的异步处理

生成器也可以被用来解决异步的问题,配合Promise可以让我们更优雅的书写异步代码

回调函数+生成器

写一个函数用来延迟2s获取数据,这是一个异步的函数,这个函数传入2个参数,成功的callback和失败的callback

        function fetchData(successCallback, failedCallback) {
            const data = [
                '迭代器 需要满足迭代协议',
                '对象默认不可以迭代 Arrary Map Set等等 可以迭代',
                '其中 可迭代对象需要实现迭代器',
                '即 obj[Symbol.iterator] = 工厂函数 返回一个可迭代对象',
                '可迭代对象需要包含 next函数,next函数需要返回对象 {value , done }',
                '可迭代对象可以用 for of 遍历',
                '区分 for in 会遍历原型链上enumable Object.keys 不会遍历原型链'
            ]
            setTimeout(() => {
                successCallback(data)
                // failedCallback('inernal server error!')
            }, 2000);
        }

写一个函数用来调用改函数获取数据并且以alert的形式展示

        function* queryData() {
            try {
                const data = yield fetchData((data) => {
                    console.log(gen.next(data))
                }, (errMsg) => {
                    gen.throw(errMsg)
                })
                const str = data?.join('')
                return str
            } catch (e) {
                alert(e)
            }
        }


        var gen = queryData()
        gen.next()

当生成器在第一个next开始执行之后,会卡在 yield fetch... 的位置,当fetch异步执行结束之后,再调用gen.next() 继续执行,如果执行失败则抛出异常,此时data才会接到异步获取的值 或者trycatche才能处理异常,这样就做到了让函数 “等待” 异步事件结束返回之后才继续执行。但是这种方式不够优雅,看着很凌乱,因为生成器函数是否继续执行是由fetch方法控制的,我们需要使用Promise的方式反转控制

Promise+生成器

将上述fetch方法改成Promise形式

        function fetchData() {
            const data = [
                '迭代器 需要满足迭代协议',
                '对象默认不可以迭代 Arrary Map Set等等 可以迭代',
                '其中 可迭代对象需要实现迭代器',
                '即 obj[Symbol.iterator] = 工厂函数 返回一个可迭代对象',
                '可迭代对象需要包含 next函数,next函数需要返回对象 {value , done }',
                '可迭代对象可以用 for of 遍历',
                '区分 for in 会遍历原型链上enumable Object.keys 不会遍历原型链'
            ]
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    // resolve(data)
                    reject('500 internal server error')
                }, 2000);
            })
        }

写一个函数调用fetch并且展示

        function* queryData() {
            try {
                const data = yield fetchData()
                const resStr = data.join('')
                return resStr
            }
            catch (e) {
                alert(e)
            }
        }

需要写一个 runGenerator方法来控制生成器函数的流程

        function runGenerator(genFunc) {
            const args = [].slice.call(arguments, 1)
            //  创建生成器
            const generator = genFunc.apply(this, arguments)
            return Promise.resolve((function handleNext(value) {
                const genItem = generator.next(value)
                if (genItem.done) {
                    return genItem.value
                } else {
                    return Promise.resolve(genItem.value).then(
                        handleNext,
                        (e) => generator.throw(e)
                    )
                }
            })())
        }

这个函数完成了生成器的创建,等待promise返回后继续调用next,判断生成器函数是否结束的流程,最终函数的返回结果是个promise。

通过使用promise的方式,实现了生成器函数的流程控制不取决于fetch(第三方库)实现了控制反转,让代码也更优雅,类似于async await 的写法。 

async&await

async & await 本质上就是生成器函数+Promise的写法,通过babel我们可以看到async await转换之后的写法

async function fetchData(){
	const data = await fetch()
    consoel.log(data)
}

转换后如下: asyncToGenerator正式上面的 runGenerator的写法,asyncGeneratorStep被单独拆分,实际就是上面的hanleNext写法,只不过我们使用async await时,不需要自己写这个控制函数,使用webpack等工具会帮我们生成。

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}
function _asyncToGenerator(fn) {
  return function () {
    var self = this,
      args = arguments;
    return new Promise(function (resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      }
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      }
      _next(undefined);
    });
  };
}

function fetchData() {
  return _fetchData.apply(this, arguments);
}

function _fetchData() {
  _fetchData = _asyncToGenerator(
    /*#__PURE__*/ _regeneratorRuntime().mark(function _callee() {
      var data;
      return _regeneratorRuntime().wrap(function _callee$(_context) {
        while (1)
          switch ((_context.prev = _context.next)) {
            case 0:
              _context.next = 2;
              return fetch();
            case 2:
              data = _context.sent;
              consoel.log(data);
            case 4:
            case "end":
              return _context.stop();
          }
      }, _callee);
    })
  );
  return _fetchData.apply(this, arguments);
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值