一. 迭代器详解
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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。