Iterator(迭代器)-Generator(生成器)详解
此篇文章主要讲解了Iterator(迭代器)-Generator(生成器),在实际开发中用的不是很多,但是对于理解async/await有一定的帮助,同时对React中的redux库的使用,有很大的帮助
大家在阅读文章的时候,有写错的地方,还请多多指正!
什么是迭代器
- 使用户 在容器对象上遍访的对象,是用该接口,无需关系对象的内部实现过程
- 我们可以明白,迭代器就是可以帮助我们对某个数据结构进行遍历的对象
- 在JavaScript中,迭代器也是一个具体的对象,需要符合协议
- 该对象中,应该有一个next方法,定义该方法的要求如下
- 该方法是无参数或者接收一个参数,并且返回两个对象
- done(boolean):如果迭代器可以产生序列中的下一个值,则为false;如果迭代完毕,则为true
- value:迭代器返回的值,done为true时候,可以省略
- 该对象中,应该有一个next方法,定义该方法的要求如下
//在js中,我们知道数组可以遍历
let arr = [1,2,3]
//是因为我们在创建数组的时候,js内部帮我们用了迭代器,生成了可迭代对象
- 接下来我们就要实现以下,js内部迭代器的工作原理(首先给一个arr数组创建迭代器)
- 有next方法
- 该方法返回done和value两个属性
let arr = [1, 2, 3, 4];
//此时iteratorArr就是arr的迭代器
let iteratorArr = {
index: 0,
next() {
//没有遍历完的时候,done为false,value为arr[i]
if (this.index < arr.length) {
return { done: false, value: arr[this.index++] };
} else {
//遍历完成只会,done为false,value可以省略
return { done: true };
}
},
};
console.log(iteratorArr.next());
console.log(iteratorArr.next());
console.log(iteratorArr.next());
console.log(iteratorArr.next());
console.log(iteratorArr.next());
- 那么接下来我们要封装一个函数,可以为所有的数组创建迭代器
- 首先该函数可以接收一个参数:为哪个数组创建
- 之后一个对象,对象中有next方法
let arr = [1, 2, 3, 4];
function createIteratorArr(arrName) {
index = 0;
//返回一个对象,对象中包含next方法,所以可以链式调用
return {
next() {
if (index < arrName.length) {
return { done: false, value: arrName[index++] };
} else {
//遍历完成只会,done为false,value可以省略
return { done: true };
}
},
};
}
let arr1 = createIteratorArr(arr);
console.log(arr1.next());
console.log(arr1.next());
console.log(arr1.next());
可迭代对象
刚刚我们了解了迭代器的原理,那么什么是可迭代对象呢?
可迭代对象与迭代器是不同的概念
当一个对象实现了iterable protocol协议的时候,就是一个可迭代对象
这个对象要求必须事项@@iterator方法,在代码中我们使用Symbol.iterator访问该属性
- 首先我们知道,一个普通的对象,是不可以被迭代的,那么我们能否为其创建一个迭代器,去迭代里面的内容呢?
//创建一个迭代器,迭代obj中的num属性
let obj = {num:[1,2,3,]}
let iteratorArr = {
index: 0,
next() {
//没有遍历完的时候,done为false,value为arr[i]
if (this.index < obj.num.length) {
return { done: false, value: obj.num[this.index++] };
} else {
//遍历完成只会,done为false,value可以省略
return { done: true };
}
},
};
- 以上代码实现了obj.num的迭代,但是对象和迭代器是分开的,我们能否将两个合并,让迭代器在对象的内部
let obj = {
arr: [1, 2, 3, 4],
//这是迭代器函数固定的命名[Symbol.iterator]
[Symbol.iterator]() {
//迭代器本身是一个对象,对象中包含next方法
return {
index: 0,
next() {
//没有遍历完的时候,done为false,value为arr[i]
if (this.index < obj.arr.length) {
return { done: false, value: obj.arr[this.index++] };
} else {
//遍历完成只会,done为false,value可以省略
return { done: true };
}
},
};
},
};
let objIter = obj[Symbol.iterator]();
console.log(objIter.next());
console.log(objIter.next());
console.log(objIter.next());
- 通过以上操作,此时obj就是一个可迭代对象,因此可以使用for of进行遍历了
for (const item of obj) {
console.log(item);
}
//1 2 3 4
- 而数组本身就是一个可迭代的对象,因此,我们可以用另外一种方式遍历数组
let arr = [1, 2, 3, 4];
//获取迭代器
let arrIter = arr[Symbol.iterator]();
console.log(arrIter.next());
console.log(arrIter.next());
console.log(arrIter.next());
/**{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false } */
迭代对象的优化
- 上面我们将
obj
制作成了可迭代对象,那么我们能否对其进行优化 - 上面我们直接用的
obj.arr
,如果对象使用了别的名字,那么就需要进行更改- 那么我们就可以想到使用this
[Symbol.iterator]
函数是被obj调用,所以在[Symbol.iterator]
函数的内部this就是obj- 而next是被
[Symbol.iterator]
函数调用的,所以next内部的this指向就是[Symbol.iterator]
函数 - 两种解决方案:将
[Symbol.iterator]
传递到next中;将next变成箭头函数
//将 ``[Symbol.iterator]``传递到next中;
let obj = {
arr: [1, 2, 3, 4],
//这是迭代器函数固定的命名[Symbol.iterator]
[Symbol.iterator]() {
const _this = this;
//迭代器本身是一个对象,对象中包含next方法
return {
index: 0,
next() {
//没有遍历完的时候,done为false,value为arr[i]
if (this.index < _this.arr.length) {
return { done: false, value: _this.arr[this.index++] };
} else {
//遍历完成只会,done为false,value可以省略
return { done: true };
}
},
};
},
};
for (const item of obj) {
console.log(item);
}
//将next变成箭头函数
let obj = {
arr: [1, 2, 3, 4],
//这是迭代器函数固定的命名[Symbol.iterator]
[Symbol.iterator]() {
//迭代器本身是一个对象,对象中包含next方法
let index = 0;
return {
next: () => {
//没有遍历完的时候,done为false,value为arr[i]
if (index < this.arr.length) {
return { done: false, value: this.arr[index++] };
} else {
//遍历完成只会,done为false,value可以省略
return { done: true };
}
},
};
},
};
for (const item of obj) {
console.log(item);
}
- 那么要对键值对进行遍历,该怎么操作
let object = {
name: "zhangcheng",
age: 18,
[Symbol.iterator]() {
//可以遍历keys单独获取键
let keys = Object.keys(this);
//可以遍历values单独获取值
let values = Object.values(this);
let index = 0;
//使用entries可以获取键值对
let keyValues = Object.entries(this);
const iterator = {
next: () => {
if (index < keys.length) {
return { done: false, value: keyValues[index++] };
} else {
return { done: true };
}
},
};
return iterator;
},
};
for (const iterator of object) {
console.log(iterator);
}
原生可迭代对象
String Array Map Set argument对象 NodeList集合
都是可迭代对象
可迭代对象应用
- 在JS的特定语法中
- for…of 展开语法 yield 解构赋值
//展开语法这里需要特别说明一下
let obj = {a:100}
let obj2 = {...obj}//这种是浏览器做了特殊处理,在ES8左右新增的,可以在创建对象字面量的时候使用展开运算符
function foo(a,b){
}
foo(...obj)//在这里运用展开运算符的时候就会报错
- 在创建一些对象的时候
new Map new WeakMap new Set new WeakSet
- 一些方法的调用
Promise.all Promise.race Array.from
自定义类的迭代
创建一个类,由这个类创建的所有对象都是可以迭代的
class Person {
constructor(name, age, friends) {
this.name = name;
this.age = age;
this.friends = friends;
}
//在实例对象上创建实例方法
[Symbol.iterator]() {
let index = 0;
//对对象的key进行迭代
let keys = Object.keys(this);
const iterator = {
next: () => {
if (index < keys.length) {
return { done: false, value: keys[index++] };
} else {
return { done: true };
}
},
};
return iterator;
}
}
let p = new Person("zhangcheng", 18, "jame");
for (const iterator of p) {
console.log(iterator);
}
迭代器的中断
当使用for循环遍历的时候,其内部使用了break、return、throw等中断了遍历,可以在迭代器中使用return函数,监听此类操作
class Person {
constructor(name, age, friends) {
this.name = name;
this.age = age;
this.friends = friends;
}
//在实例对象上创建实例方法
[Symbol.iterator]() {
let index = 0;
//对对象的key进行迭代
let keys = Object.keys(this);
const iterator = {
next: () => {
if (index < keys.length) {
return { done: false, value: keys[index++] };
} else {
return { done: true };
}
},
//终止的时候,默认执行return函数
return: () => {
console.log("终止了");
//函数内部需要返回一个对象,否则会报错
return { done: true };
},
};
return iterator;
}
}
let p = new Person("zhangcheng", 18, "jame");
for (const iterator of p) {
break;
}
什么是生成器
学习生成器,对于我们理解async/await的理解会有帮助;同时对于React中的redux库的使用,也会有很大的帮助
生成器是ES6新增的一种函数控制、使用的方案,可以让我们灵活的控制函数什么时候继续执行、暂停执行
生成器是特殊的迭代器
- 生成器也是一个函数,但是和普通的函数有区别
- 首先,生成器函数需要在 **function关键字后面加 ***
- 生成器可以通过yield来控制函数的暂停
- 生成器函数默认执行的时候,返回的是一个生成器对象
生成器函数的基本使用
- 生成器函数返回的是生成器对象
- 调用生成器对象的next()方法,会真正执行函数
- 遇到 yield 就会暂停
function* foo() {
console.log(111);
console.log(222);
yield;
console.log(333);
yield;
console.log(444);
console.log(555);
console.log(666);
console.log(777);
}
const generator = foo();
generator.next(); //111 222
generator.next(); //111 222 333
generator.next(); //全部打印
生成器函数参数和返回值
- 生成器是特殊的 迭代器,因此在调用next方法的时候,是有返回值的
- 是通过yield拿到其返回值:
yield value
function* foo() {
console.log(111);
console.log(222);
yield "bbb";
console.log(333);
yield "ccc";
console.log(444);
console.log(555);
console.log(666);
console.log(777);
}
const generator = foo();
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());
/*
111
222
{ value: 'bbb', done: false }
333
{ value: 'ccc', done: false }
444
555
666
777
{ value: undefined, done: true }
*/
- 通过上述代码,我们可以看到,next方法的返回值就是之前迭代器next方法的返回值
- 当函数内部执行完成之后,返回的
done:true
- 同理,若在函数中途return出去了,则done也为true,value就是return的结果,且后面的代码不会执行
function* foo() {
console.log(111);
console.log(222);
yield "bbb";
console.log(333);
return 0;
yield "ccc";
console.log(444);
console.log(555);
console.log(666);
console.log(777);
}
const generator = foo();
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());
/*
111
222
{ value: 'bbb', done: false }
333
{ value: 0, done: true }
{ value: undefined, done: true }
*/
- 那么我们想在next方法中传入参数,并将这个参数在函数的内部执行,又该怎么操作
- 一般第一个next方法不会传值
- 第一个next方法执行的时候,函数内部代码的参数,是由生成器函数传入的
- 第二个next方法中开始传入参数,其参数是给生成器函数内部第一个yield后面的代码,第三个next方法参数,给第二个yield后面的代码
- 在函数内部接收参数的时候
const 变量名 = yield 返回值
,变量是传递给下面的代码的
function* foo(next1) {
console.log(111, next1);
console.log(222, next1);
const next2 = yield "bbb";
console.log(333, next2);
const next3 = yield "ccc";
console.log(444, next3);
console.log(555, next3);
console.log(666, next3);
console.log(777, next3);
}
const generator = foo("我是第一个参数");
generator.next();
generator.next("我是第二个参数");
generator.next("我是第三个参数");
/*
111 我是第一个参数
222 我是第一个参数
333 我是第二个参数
444 我是第三个参数
555 我是第三个参数
666 我是第三个参数
777 我是第三个参数
*/
生成器中断(用的比较少)
迭代器中由return方法,可以是迭代器中断,因此在生成器中,我们可以直接调用return方法或者throw一个error来终止函数的执行
function* foo(next1) {
console.log(111, next1);
console.log(222, next1);
const next2 = yield "bbb";
console.log(333, next2);
const next3 = yield "ccc";
console.log(444, next3);
console.log(555, next3);
console.log(666, next3);
console.log(777, next3);
}
const generator = foo("我是第一个参数");
generator.next();
generator.return("我是第二个参数"); //后面的函数不会执行,但是next会有返回值
generator.throw(new Error("我是一个错误"))//与return执行逻辑是一样的,但是需要捕获异常
generator.next("我是第三个参数");
生成器代替迭代器的应用场景
结合以上知识我们可以对之前写过的迭代器进行代替,用更少的代码实现相似的功能
- 首先 生成器函数执行的结果,会返回一个生成器对象
- 而 这个生成器对象,就是特殊的迭代器,可以直接调用next方法
- 之前使用函数 生成对应数组迭代器的函数就可以简化为以下代码
function* createGeneratorArr(arrName) {
// index = 0;
// //返回一个对象,对象中包含next方法,所以可以链式调用
// return {
// next() {
// if (index < arrName.length) {
// return { done: false, value: arrName[index++] };
// } else {
// //遍历完成只会,done为false,value可以省略
// return { done: true };
// }
// },
// };
/*
先前的做法是通过这个函数,返回一个所谓迭代器对象,通过这个对象调用next方法
我们借助生成器函数返回生成器对象的原理
*/
for (let i = 0; i < arrName.length; i++) {
yield arrName[i];
}
}
let arr = [1, 2, 3, 4, 5, 6];
let arrGenerator = createGeneratorArr(arr);
console.log(arrGenerator.next());
console.log(arrGenerator.next());
console.log(arrGenerator.next());
/**{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false } */
生成器自定义类的迭代对象
在迭代器的学习中,我们在一个类中,创建了相应的方法去生成可迭代对象,同理,我们可以用生成器对代码进行优化
- 首先需要了解
yield*
的机制- yield*实际上是yield的语法糖
- 后面接的是可迭代对象,会依次迭代对象中的每个元素
function* createGeneratorArr(arrName) {
// for (let i = 0; i < arrName.length; i++) {
// yield arrName[i];
// }
/*
刚刚的代码,我们使用了for循环对其进行yield控制
我们可以用yield* addName 直接替代该循环
*/
yield* arrName
}
- 在了解了
yield*
的机制之后,我们就可以将自定义类的代码进行优化
class Person {
constructor(name, age, friends) {
this.name = name;
this.age = age;
this.friends = friends;
}
//在实例对象上创建实例方法
// [Symbol.iterator]() {
// let index = 0;
// //对对象的key进行迭代
// let keys = Object.keys(this);
// const iterator = {
// next: () => {
// if (index < keys.length) {
// return { done: false, value: keys[index++] };
// } else {
// return { done: true };
// }
// },
// };
// return iterator;
// }
/*要用生成器函数*/
*[Symbol.iterator]() {
//迭代元素的属性
yield* Object.keys(this);
//迭代元素的值
yield* Object.values(this);
//迭代元素的键值对
yield* Object.entries(this);
}
}
let p = new Person("zhangcheng", 18, "jame");
for (const iterator of p) {
console.log(iterator);
}