第二十节:生成器/迭代器详解、生成器替代迭代器、异步处理方案

一. 迭代器详解

1. 什么是迭代器?

(1). 维基百科上的定义:是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。

        A.其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;

        B.在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等;

(2). JS中的迭代器:

   迭代器是一个具体的对象,这个对象需要符合迭代器协议,而这个协议就是需要有1个next方法, 下面是关于next方法的说明:

   A. 参数:无参或者有一个参数

   B. 返回值: return { done: true, value: "xxxx" }

      a. done(boolean)

            如果迭代器可以产生序列中的下一个值,则为 done=false。(这等价于没有指定 done 这个属性。)

            如果迭代器已将序列迭代完毕,则为 done=true。此时,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值undefined。

      b. value

            迭代器返回的任何 JavaScript 值。done 为 true 时可省略。

代码分享:

复制代码
{
    console.log("------- 1. 什么是迭代器?--------");
    // 1.1 编写一个迭代器
    const myIterator = {
        next() {
            return { done: true, value: "ypf" };
        },
    };

    // 1.2 用迭代器遍历数组
    const myNames = ["ypf1", "ypf2", "ypf3"];
    let index = 0;
    const myNamesIterators = {
        next() {
            if (index < myNames.length) {
                return { done: false, value: myNames[index++] };
            } else {
                return { done: true, value: undefined };
            }
        },
    };
    // 调用
    console.log(myNamesIterators.next()); //{ done: false, value: 'ypf1' }
    console.log(myNamesIterators.next()); //{ done: false, value: 'ypf2' }
    console.log(myNamesIterators.next()); //{ done: false, value: 'ypf3' }
    console.log(myNamesIterators.next()); //{ done: true, value: undefined }
    console.log(myNamesIterators.next()); //{ done: true, value: undefined }
}
复制代码

2. 迭代器函数实操

  封装一个函数,函数返回值是一个迭代器,函数的参数是1个数组,通过返回的迭代器进行遍历传入的数组。

代码分享:

复制代码
{
    console.log("------- 2. 迭代器函数实操--------");
    function myIteratorFn(array) {
        let index = 0;
        return {
            next() {
                if (index < array.length) {
                    return { done: false, value: array[index++] };
                } else {
                    return { done: true, value: undefined };
                }
            },
        };
    }
    const myNames = ["ypf1", "ypf2", "ypf3"];
    const myFn = myIteratorFn(myNames);
    // 调用
    console.log(myFn.next()); //{ done: false, value: 'ypf1' }
    console.log(myFn.next()); //{ done: false, value: 'ypf2' }
    console.log(myFn.next()); //{ done: false, value: 'ypf3' }
    console.log(myFn.next()); //{ done: true, value: undefined }
    console.log(myFn.next()); //{ done: true, value: undefined }
}
复制代码

3. 可迭代对象

(1).概念

       它和迭代器是不同的概念,当一个对象实现了 @@iterator 方法,该方法的返回值是一个迭代器,在代码中我们使用 Symbol.iterator 访问该属性,那么他就是一个可迭代对象

(2).作用

      当一个对象变成一个可迭代对象的时候,进行某些迭代操作,比如 for...of 操作时,其实就会调用它的@@iterator 方法;

      同时也说明了 for-of 遍历的都是可迭代对象

代码分享:

复制代码
{
    console.log("-------3. 可迭代对象--------");
    // 3.1 创建一个可迭代对象
    const iteratorObj = {
        myNames: ["ypf1", "ypf2", "ypf3"],
        [Symbol.iterator]: function () {
            let index = 0;
            return {
                // 特别注意:这里必须用箭头函数,this指向它的上一层作用域,即this和index是平级的,所以this执行iterableObj
                next: () => {
                    if (index < this.myNames.length) {
                        return { done: false, value: this.myNames[index++] };
                    } else {
                        return { done: true, value: undefined };
                    }
                },
            };
        },
    };
    // 3.2 第一次调用
    console.log("---------- 3.2 第一次调用----------");
    const iterator1 = iteratorObj[Symbol.iterator]();
    console.log(iterator1.next()); //{ done: false, value: 'ypf1' }
    console.log(iterator1.next()); //{ done: false, value: 'ypf2' }
    console.log(iterator1.next()); //{ done: false, value: 'ypf3' }
    console.log(iterator1.next()); //{ done: true, value: undefined }
    console.log(iterator1.next()); //{ done: true, value: undefined }

    // 3.3 第二次调用【生成一个全新的迭代器】
    console.log("---------- 3.3 第二次调用----------");
    const iterator2 = iteratorObj[Symbol.iterator]();
    console.log(iterator2.next()); //{ done: false, value: 'ypf1' }
    console.log(iterator2.next()); //{ done: false, value: 'ypf2' }
    console.log(iterator2.next()); //{ done: false, value: 'ypf3' }
    console.log(iterator2.next()); //{ done: true, value: undefined }
    console.log(iterator2.next()); //{ done: true, value: undefined }

    // 3.4 for-of 遍历可迭代对象
    console.log("----------3.4 for-of 遍历可迭代对象----------");
    for (const item of iteratorObj) {
        console.log(item); //输出 ypf1,ypf2,ypf3
    }
}
复制代码

4. 原生的可迭代对象

    事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个可迭代的:

    String、Array、Map、Set、arguments对象、NodeList集合;

代码分享:
复制代码
{
    console.log("------4. 原生的可迭代对象------");
    // 4.1 数组是可迭代对象
    console.log("------4.1 数组是可迭代对象------");
    const names = ["ypf1", "ypf2", "ypf3"];
    const iterator1 = names[Symbol.iterator]();
    console.log(iterator1.next()); //{ value: 'ypf1', done: false }
    console.log(iterator1.next()); //{ value: 'ypf2', done: false }
    console.log(iterator1.next()); //{ value: 'ypf3', done: false }
    console.log(iterator1.next()); //{ value: undefined, done: true }
    // 数组本身是个可迭代对象,也就解释了为什么可以 for of遍历了
    for (const item of names) {
        console.log(item);
    }
    //  4.2. Map/Set是可迭代对象
    console.log("------ 4.2. Map/Set是可迭代对象------");
    const set = new Set();
    set.add(10);
    set.add(100);
    set.add(1000);
    const iterator2 = set[Symbol.iterator]();
    console.log(iterator2.next()); //{ value: 10, done: false }
    console.log(iterator2.next()); //{ value: 100, done: false }
    console.log(iterator2.next()); //{ value: 1000, done: false }
    console.log(iterator2.next()); //{ value: undefined, done: true }
    for (const item of set) {
        console.log(item);
    }

    // 4.3 函数中arguments也是一个可迭代对象
    console.log("-----4.3 函数中arguments也是一个可迭代对象-----");
    function foo(x, y, z) {
        console.log(arguments[Symbol.iterator]); //Function
        for (const arg of arguments) {
            console.log(arg);
        }
    }
    foo(10, 20, 30);
}
复制代码

5. 可迭代对象的应用

(1).JavaScript中语法:for ...of遍历、展开语法(spread syntax)、yield*(后面讲)、解构赋值(Destructuring_assignment);

(2).创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);

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

代码分享: 

复制代码
{
    // 5.1 for-of遍历场景,不再介绍

    // 5.2 展开语法
    console.log("---------5.2 展开语法---------");
    const iterableObj = {
        names: ["ypf1", "ypf2", "ypf3"],
        [Symbol.iterator]: function () {
            let index = 0;
            return {
                next: () => {
                    if (index < this.names.length) {
                        return { done: false, value: this.names[index++] };
                    } else {
                        return { done: true, value: undefined };
                    }
                },
            };
        },
    };
    const names = ["ypf4", "ypf5", "ypf6"];
    const newNames = [...names, ...iterableObj];
    console.log(newNames); //[ 'ypf4', 'ypf5', 'ypf6', 'ypf1', 'ypf2', 'ypf3' ]

    // 5.3 对象的展开,ES9新增的特性,这里不是迭代器!!
    console.log(
        "------- 5.3 对象的展开,ES9新增的特性,这里不是迭代器!!------"
    );
    const obj = { name: "ypf", age: 18 };
    const newObj = { ...obj };
    console.log(newObj);

    // 5.4 解构-数组--可迭代对象
    console.log("-----5.4 解构-数组------");
    const [name1, name2] = names;
    console.log(name1, name2);
// 注意:对象的解构不是迭代器,是ES9中新增的特性 const { name, age } = obj; console.log(name, age); // 5.5 创建一些其它对象 const set1 = new Set(iterableObj); const set2 = new Set(names); const array1 = Array.from(iterableObj); // 5.6 Promist.all console.log(" ------5.6 Promist.all--------"); Promise.all(iterableObj).then(res => console.log(res)); }
复制代码

 

二. 生成器详解

1. 什么是生成器?

 生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。

 生成器函数也是一个函数,但是和普通的函数有一些区别:

(1).生成器函数需要在function的后面加一个符号:*

(2).生成器函数可以通过yield关键字来控制函数的执行流程

(3).生成器函数的返回值是一个Generator(生成器)

注:生成器事实上是一种特殊的迭代器。

代码分享:

{
	console.log("-------1. 生成器函数-------");
	function* foo() {
		console.log("foo函数开始执行");
		const value1 = 100;
		console.log("第一段代码:", value1);
		yield;
		const value2 = 200;
		console.log("第二段代码:", value2);
		yield;
		const value3 = 300;
		console.log("第三段代码:", value3);
		yield;
		console.log("foo函数执行结束");
	}
	// 调用生成器函数,返回一个生成器对象
	const generator = foo();

	// 1.1 第一次执行,执行的是第一个yield上面的代码
	console.log("------ 1.1 第一次执行,执行的是第一个yield上面的代码--------");
	generator.next();
	// 1.2 第二次执行,执行的是第2个yield和第1个之间的代码
	console.log("------  1.2 第二次执行,执行的是第2个yield和第1个之间的代码--------");
	generator.next();
	// 1.3 第三次执行,执行的是第3个yield和第1个之间的代码
	console.log("------  1.3 第三次执行,执行的是第3个yield和第1个之间的代码--------");
	generator.next();
	// 1.4 第四次执行,执行的是第4个yield之后代码
	console.log("------ 1.4 第四次执行,执行的是第4个yield之后代码-------");
	generator.next();
}

2. 生成器函数及执行流程

(1). 当遇到yeild时候,暂停函数的执行,第一次next,执行的是第一个yield之前的代码,第二次next,执行的是第2个next和第1个next之间的代码

(2). yeild后面可以跟内容值,即作为next方法的返回值, 返回到是一个 {done;true,value='xxx'}的对象

代码分享:

{
	console.log("-----2. next方法的返回值-----");
	function* foo() {
		console.log("foo函数开始执行");
		const value1 = 100;
		console.log("第一段代码:", value1);
		yield value1 * 10;
		const value2 = 200;
		console.log("第二段代码:", value2);
		yield value2 * 10;
		const value3 = 300;
		console.log("第三段代码:", value3);
		yield value3 * 10;
		console.log("foo函数执行结束");
	}
	// 调用生成器函数,返回一个生成器对象
	const generator = foo();

	// 1.1 第一次执行,执行的是第一个yield上面的代码
	console.log("------ 1.1 第一次执行,执行的是第一个yield上面的代码--------");
	const result1 = generator.next();
	console.log("result1:", result1.done, result1.value); //false 1000
	// 1.2 第二次执行,执行的是第2个yield和第1个之间的代码
	console.log("------  1.2 第二次执行,执行的是第2个yield和第1个之间的代码--------");
	const result2 = generator.next();
	console.log("result2:", result2.done, result2.value); //false 2000
	// 1.3 第三次执行,执行的是第3个yield和第1个之间的代码
	console.log("------  1.3 第三次执行,执行的是第3个yield和第1个之间的代码--------");
	const result3 = generator.next();
	console.log("result3:", result3.done, result3.value); //false 3000
	// 1.4 第四次执行,执行的是第4个yield之后代码
	console.log("------ 1.4 第四次执行,执行的是第4个yield之后代码-------");
	const result4 = generator.next();
	console.log("result4:", result4.done, result4.value); //true,undefined
}

3. 通过next()方法传递参数

   我们在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值;

   注意:也就是说我们是为本次的函数代码块执行提供了一个值;

如下案例:

(1). const result2 = generator.next("ypf2");  //这里的ypf2传递给了上面的value1 ! result2是next方法的返回值是 value1+'bbb'

(2). const result3 = generator.next("ypf3"); //这里的ypf3传递给了上面的value3! result3是next方法的返回值是 value2+'ccc'

代码分享:

{
	console.log("-------3. 通过next()方法传递参数---------");
	function* foo(text) {
		console.log("foo函数开始执行");
		const value1 = yield text + "aaa";
		const value2 = yield value1 + "bbb";
		const value3 = yield value2 + "ccc";
	}
	// 调用
	const generator = foo("ypf1");
	const result1 = generator.next();
	console.log("result1:", result1.done, result1.value); //false, ypf1aaa

	// 测试next的参数传递
	const result2 = generator.next("ypf2"); //这里的ypf2传递给了上面的value1!!!!!!!
	console.log("result2:", result2.done, result2.value); //false, ypf2aaa

	const result3 = generator.next("ypf3"); //这里的ypf3传递给了上面的value3!!!!!!!
	console.log("result3:", result3.done, result3.value); //false, ypf3aaa
}

4. return终止执行

   可以给生成器函数传递参数的方法是通过return函数

注:return传值后这个生成器函数就会结束,之后调用next不会继续生成值了

如下案例:const result2 = generator.return("ypf2");  结果: result2={done=true,value='ypf2aaa'}

代码分享:

{
	console.log("------4. return终止执行--------");
	function* foo(text) {
		console.log("foo函数开始执行");
		const value1 = yield text + "aaa";
		const value2 = yield value1 + "bbb";
		const value3 = yield value2 + "ccc";
	}
	// 调用
	const generator = foo("ypf1");
	const result1 = generator.next();
	console.log("result1:", result1.done, result1.value); //false, ypf1aaa

	// 测试return的参数传递
	const result2 = generator.return("ypf2"); //这里的ypf2传递给了上面的value1!!!!!!!
	console.log("result2:", result2.done, result2.value); //true, ypf2aaa

	// 后面的生成器就结束了,不再获取值了
	const result3 = generator.next("ypf3"); //
	console.log("result3:", result3.done, result3.value); //true, undefined
}

5. throw抛出异常

除了给生成器函数内部传递参数之外,也可以给生成器函数内部抛出异常:

(1).抛出异常后我们可以在生成器函数中捕获异常;

(2).在catch语句中不能继续yield新的值了,但可以在catch语句外使用yield继续中断函数的执行;

{
	console.log("------ 5. throw抛出异常--------");
	function* foo() {
		console.log("代码开始执行~");
		const value1 = 100;
		try {
			yield value1;
		} catch (error) {
			console.log("捕获到异常情况:", error);
			yield "abc";
		}

		console.log("第二段代码继续执行");
		const value2 = 200;
		yield value2;

		console.log("代码执行结束~");
	}
	const generator = foo();
	const result = generator.next();
	generator.throw("error message");
}

 

三. 生成器 代替 迭代器

写法1:

   遍历数组,然后使用yield返回

{
	console.log("-------生成器替代迭代器-------");
	function* createArrayIterator(arr) {
		// 写法1
		// for (const item of arr) {
		// 	yield item;
		// }
		// 写法2(语法糖)
		yield* arr;
	}
	// 调用
	const names = ["ypf1", "ypf2", "ypf3"];
	const nameIterator = createArrayIterator(names);
	console.log(nameIterator.next()); // { value: 'ypf1', done: false }
	console.log(nameIterator.next()); // { value: 'ypf2', done: false }
	console.log(nameIterator.next()); // { value: 'ypf3', done: false }
	console.log(nameIterator.next()); // { value: undefined, done: true }
}

写法2:

   使用yield*来生产一个可迭代对象:这个时候相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值;

{
	console.log("案例2-创建一个函数, 这个函数可以迭代一个范围内的数字");
	// 写法1:迭代器写法
	/* 	function createRangeIterator(start, end) {
		let index = start;
		return {
			next() {
				if (index < end) {
					return { done: false, value: index++ };
				} else {
					return { done: true, value: undefined };
				}
			},
		};
	} */

	// 写法2-生成器写法
	function* createRangeIterator(start, end) {
		let index = start;
		while (index < end) {
			yield index++;
		}
	}
	// 调用
	const rangeIterator = createRangeIterator(5, 8);
	console.log(rangeIterator.next()); //{ value: 5, done: false }
	console.log(rangeIterator.next()); //{ value: 6, done: false }
	console.log(rangeIterator.next()); //{ value: 7, done: false }
	console.log(rangeIterator.next()); //{ value: undefined, done: true }
}

 

四. 异步处理方案

1. 需求

    需要多次调用接口,每次拿到返回值后,作为下次“调用的参数的一部分”进行传递。

// 封装方法
function requestData(myData) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve(myData + "ypf");
		}, 2000);
	});
}

2. 解决方案:

(1). 多次回调

// 方案1:多次回调
// 缺点:回调低于
{
	requestData("ypf").then(res => {
		requestData(res + "aaa").then(res => {
			requestData(res + "bbb").then(res => {
				console.log(res);
			});
		});
	});
}

(2). Promise中的then返回值来解决

// 方案2:Promise中的then返回值来解决
// 缺点:代码不是很好理解
{
	requestData("why")
		.then(res => {
			return requestData(res + "aaa");
		})
		.then(res => {
			return requestData(res + "bbb");
		})
		.then(res => {
			console.log(res);
		});
}

(3). Promise + 生成器

// 方案3:Promise + 生成器 【这是重点,也是难点,便于理解 async和await的原理】
{
	function* getData() {
		const res1 = yield requestData("ypf");
		const res2 = yield requestData(res1 + "aaa");
		const res3 = yield requestData(res2 + "bbb");
		const res4 = yield requestData(res3 + "ccc");
		console.log(res4);
	}
}

(4). async + await

// 方案4: async + await
{
	async function getData() {
		const res1 = await requestData("ypf");
		const res2 = await requestData(res1 + "aaa");
		const res3 = await requestData(res2 + "bbb");
		const res4 = await requestData(res3 + "ccc");
		console.log(res4);
	}
}

3. 难点拓展

(1). 手写生成器


// 难点1:手写生成器函数
{
	const generator = getData();
	generator.next().value.then(res => {
		generator.next(res).value.then(res => {
			generator.next(res).value.then(res => {
				generator.next(res);
			});
		});
	});
}

(2). 自动执行

// 难点2:自己封装一个自动执行的函数
{
	function execGenerator(genFn) {
		const generator = genFn();
		function exec(res) {
			const result = generator.next(res);
			if (result.done) {
				return result.value;
			}
			result.value.then(res => {
				exec(res);
			});
		}
		exec();
	}
	// 调用
	execGenerator(getData);
}

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

 

posted @   Yaopengfei  阅读(154)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2021-04-06 中间件专题(RabbitMQ+Kafka+MongoDB)
2020-04-06 第九节:SQLServer函数大全(二)--类型转换函数、日期函数
点击右上角即可分享
微信分享提示