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


    一. 迭代器详解

    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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    在vs2008中集成JavaScript Lint检查JavaScript语法
    (转载)SQL分页语句
    设置出错页
    判断2个输入框至少输入1个
    C#获取用户桌面等特殊系统路径
    创建存储过程的代码
    SqlParameter关于Like的传参数无效问题
    (转)利用Office里面的OWC组件进行画图
    firefox3不能获得html file的全路径的问题
    (转)使用ASP.NET上传图片汇总
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/16104830.html
Copyright © 2020-2023  润新知