迭代器(Iterator)和生成器(Generator)
前言
最早知道迭代器是在学java
的时候,那时候遍历map只能使用迭代器。具体怎么使用我现在忘得差不多了。其他语言应该大部分都有迭代器,甚至SQL也有cursor(游标)
。
存在即有意义。
没有迭代器的日子
循环语句迭代数据的时候,需要初始化一个对象来临时记录这个值。
let array = [1,2,3];
for(let i = 0; i < array.length ; i++){
var result = array[i];
}
如上述result
既记录迭代过程中的每个数据。
**优点:**语法简单;非优点:需要自己处理下标、一次性循环完数据,没有阻塞。
感觉这几个点也不算什么优缺点,为什么要有迭代器和生成器呢?
一、 迭代器
1、迭代器是对象
迭代器内部有迭代专用接口和方法,所有的迭代器都会有一个 next
方法,返回一个对象
function next(){
return {
value:[any],// 迭代的数据
done:[boolean]//是否迭代完毕
}
}
每次调用 next下标会一直往下移动,直到被迭代完毕,往后迭代都是最后一个值。
2、简易迭代器
var getIterator = function (array) {
var index = 0;
var length = array.length;
return {
next: function () {
var done = index >= length;
var value = done ? undefined : array[index++]
return {
done : done,
value : value
}
}
}
}
var iterator = getIterator(["A","B","C"]);
console.log(iterator.next());//{done: false, value: "A"}
console.log(iterator.next());//{done: false, value: "B"}
console.log(iterator.next());//{done: false, value: "C"}
console.log(iterator.next());//{done: true, value: undefined}
console.log(iterator.next());//{done: true, value: undefined}
以上是通用型对数组迭代,只适合特定结构的迭代。为了生成迭代器对象,ES6创造了生成器:
二、生成器
1、什么是生成器
生成器是返回迭代器的函数,通过 function
后面加个*
来表示,函数体内部使用yield
来表示下次迭代出来的数据:
const getIterator = function *() {
yield "A";
yield "B";
yield "C";
}
const iterator = getIterator();
console.log(iterator.next());//{done: false, value: "A"}
console.log(iterator.next());//{done: false, value: "B"}
console.log(iterator.next());//{done: false, value: "C"}
console.log(iterator.next());//{done: true, value: undefined}
console.log(iterator.next());//{done: true, value: undefined}
2、怎么用生成器
生成器本质上是一个返回迭代器的函数。
function *()
来表示这个函数,yield
相当于return,每调用一次就会结束使用掉一个“return”
利用类似return短路的特性,我们可以这样搞事情:
const getIterator = function *() {
console.log("第1次执行next")
yield "A";
console.log("第2次执行next")
yield "B";
console.log("第3次执行next")
yield "C";
}
console.log(iterator.next());//第1次执行next {done: false, value: "A"}
console.log(iterator.next());//第2次执行next {done: false, value: "B"}
console.log(iterator.next());//第3次执行next {done: false, value: "C"}
console.log(iterator.next());//{done: true, value: undefined} 难怪后面返回的是 undefined 可能是因为最后一次“yeild”返回的是undefined
造一个1.2简易迭代器
的轮子:
const getIterator = function *(array) {
for(let i = 0 ;i < array.length; i++){
yield array[i];
}
}
3、注意事项
yield
只能在 生成器内部使用
const getIterator = function *(array) {
array.forEach(function(item){
yield item
})
}
const getIterator = function *(array) {
array.forEach(item => {
yield item
})
}
以上示例代码连webstorm的静态校验都过不去,因为这里yield在foreach的回调函数内部,因此报错了,即使是lambda表达式
- 不能使用lambda创建生成器,如:
const getIterator = *(array) => {
yield 1
}
- return 会终止迭代,提前结束迭代
const getIterator = function *() {
console.log("第1次执行next")
yield "A";
console.log("第2次执行next")
return "B";
console.log("第3次执行next")
yield "C";
}
iterator = getIterator()
console.log(iterator.next());//第1次执行next {value: "A", done: false}
console.log(iterator.next());//第2次执行next {value: "B", done: true}
console.log(iterator.next());//第3次执行next {value: undefined, done: true}
三、for-of 循环
1、迭代器的循环
迭代器都可以用for-of 来循环:
const getIterator = function *() {
yield "A";
yield "B";
yield "C";
}
const iterator = getIterator()
for(let a of iterator){
console.log(a)
}
// A B C
2、可迭代对象都可以使用for-of
进行循环。
所有集合(Array
、Set
、Map
)和字符串都是可迭代对象:
const array = [1,2];
for(let a of array){
console.log(a);
}
// 1 2
const str = "12";
for(let a of str){
console.log(a);
}
// 1 2
因为这些对象内部实现了
Array.protptype[Symbol.iterator] = *function(){
for(let data in this){
yeild data
}
}
也就是这些个对象是可迭代对象
造个类似轮子:
const myArray = function(array){
this[Symbol.iterator] = function *(){
for(let data in array){
yield data
}
}
}
var iterator = new myArray([1,2,3,4])
console.log(iterator)// myArray {Symbol(Symbol.iterator): ƒ}
for(let a of iterator){
console.log(a)
}
//1 2 3 4
3、默认迭代器
- 根据1可得for of 使用了迭代器
- 根据2可得数组等可迭代对象有个默认的迭代器
因此,我们可以这样搞:
const array = [1,2,3];
let iterator = array[Symbol.iterator]();
for(let a of iterator){
console.log(a)
}
//1 2 3
iterator = array[Symbol.iterator]();
console.log(iterator.next());
// {value : 1, done :false}
4、其他
迭代器都可以用for-of 来循环,每次循环前都会检查下是不是可迭代的内容。
校验的逻辑类似于:
const isIterator = Clazz => typeof Clazz.prototype[Symbol.iterator] === 'function';
但是也有失准的时候,比如:
const myArray = function(array){
this[Symbol.iterator] = function *(){//迭代器方法是放在对象里面,而不是原型链上,但是它确实是可迭代对象
for(let data in array){
yield data
}
}
}
console.log(isIterator(myArray));//false
因此:
const isIterator = Clazz => {
return typeof new Clazz()[Symbol.iterator] === 'function';
}
console.log(isIterator(myArray));//true
四、高级迭代功能
1、迭代器传参
- next方法传参的值会替换上次 yield返回的值:
let getIterator = function *() {
console.log("第1次执行next")
let first = yield "A";
console.log("第2次执行next")
let second = yield first + "B";
}
let iterator = getIterator();
console.log(iterator.next());//{value: "A", done: false}
console.log(iterator.next());//{value: "undefinedB", done: false}
console.log(iterator.next());//{value: undefined, done: true}
iterator = getIterator();
console.log(iterator.next(1));//{value: "A", done: false}
console.log(iterator.next(2));//{value: "2B", done: false}
console.log(iterator.next(3));//{value: undefined, done: true}
getIterator = function *() {
console.log("第1次执行next")
let first = yield "A";
console.log("第2次执行next")
let second = yield first + "B";
yield second + "C"
}
iterator = getIterator();
console.log(iterator.next(1));//{value: "A", done: false}
console.log(iterator.next(2));//{value: "2B", done: false}
console.log(iterator.next(3));//{value: "3C", done: true}
// 内部执行顺序大概是:
// iterator.next(1) => console.log("第1次执行next"); yield "A"
// iterator.next(2) => console.log("第2次执行next");let first = 2; yield first + "B";
// iterator.next(3) => console.log("第3次执行next");let second = 3; yield second + "C"
- 如上:第一次next方法无论如何传入啥都会被丢弃
2、抛出异常
- 注入错误,强行阻止往下迭代:
const getIterator = function *() { yield "A"; yield "B"; yield "C"; } iterator = getIterator(); console.log(iterator.next());//{value: "A", done: false} console.log(iterator.throw(new Error("Boom Sakalaka")));//Uncaught Error: Boom Sakalaka //以下代码不执行 console.log(iterator.next()); //如果此时还能拿到iterator,这时候 iterator.next() {value: undefined, done: true} // iterator.next() => yield "A" // iterator.throw(new Error("Boom Sakalaka")) => error,在 yield "B"之前抛出错误
- 迭代捕获
const getIterator = function *() {
let message ="";
yield "A";
try{
yield "B";
}catch(e){
message = e.toString()
}
yield message+"C";
}
iterator = getIterator();
console.log(iterator.next());//{value: "A", done: false}
console.log(iterator.next());//{value: "B", done: false}
console.log(iterator.throw(new Error("Boom Sakalaka")));//{value: "Error: Boom SakalakaC", done: false}
console.log(iterator.next());//{value: undefined, done: true}
以上例子可知:
throw也是一次迭代,也是在正常的next方法执行返回值 yield value 之前抛出错误,如果错误没有被处理则直接终止迭代。
等价于(注意throw的位置):
const getIterator = function *() {
let message ="";
yield "A";
try{
yield "B";
throw new Error("Boom Sakalaka");
}catch(e){
message = e.toString()
}
yield message+"C";
}
iterator = getIterator();
console.log(iterator.next());//{value: "A", done: false}
console.log(iterator.next());//{value: "B", done: false}
console.log(iterator.next());//{value: "Error: Boom SakalakaC", done: false}
console.log(iterator.next());//{value: undefined, done: true}
3、委托生成器
有些时候多个生成器需要合并成一个。
语法:
getIteratorFunc = function*() {
yield * 【新的迭代器】
}
- 简单委托,举个例子:
幼儿园发水果:苹果3个,香蕉2根,在两个篮子。发完了苹果,才能发香蕉。
这时候老师只能拿一个篮子,把苹果和香蕉放一个篮子,问题解决。
getAppleIterator = function*() {
yield "苹果1"
yield "苹果2"
yield "苹果3"
}
getBananaIterator = function*(){
yield "香蕉1"
yield "香蕉2"
}
apple = getAppleIterator()
console.log(apple.next());//{value: "苹果1", done: false}
console.log(apple.next());//{value: "苹果2", done: false}
console.log(apple.next());//{value: "苹果3", done: false}
banana = getBananaIterator()
console.log(banana.next());//{value: "香蕉1", done: false}
console.log(banana.next());//{value: "香蕉2", done: false}
可以写成这样:
getFruitIterator = function*(){
yield *getAppleIterator();
yield *getBananaIterator();
}
fruit = getFruitIterator();
console.log(fruit.next());//{value: "苹果1", done: false}
console.log(fruit.next());//{value: "苹果2", done: false}
console.log(fruit.next());//{value: "苹果3", done: false}
console.log(fruit.next());//{value: "香蕉1", done: false}
console.log(fruit.next());//{value: "香蕉2", done: false}
- 高端委托:利用生成器返回值处理复杂任务
getAppleIterator = function*() {
yield "苹果1"
yield "苹果2"
return 1
}
getBananaIterator = function*(count){
yield `香蕉${count}`
}
getFruitIterator = function*(){
const count = yield *getAppleIterator();
yield *getBananaIterator(count);
}
fruit = getFruitIterator();
console.log(fruit.next());//{value: "苹果1", done: false}
console.log(fruit.next());//{value: "苹果2", done: false}
console.log(fruit.next());//{value: "香蕉1", done: false}
console.log(fruit.next());//{value: undefined, done: true}
- 可委托迭代
字符串、数组啥的都可以迭代出来:
getIterator = function*() {
yield * "123"
} ;
it = getIterator();
console.log(it.next());//{value: "1", done: false}
console.log(it.next());//{value: "2", done: false}
console.log(it.next());//{value: "3", done: false}
console.log(it.next());//{value: undefined, done: true}
getIterator = function*() {
yield * [1,2,3]
} ;
it = getIterator();
console.log(it.next());//{value: 1, done: false}
console.log(it.next());//{value: 2, done: false}
console.log(it.next());//{value: 3, done: false}
console.log(it.next());//{value: undefined, done: true}
*五、异步任务执行
讲了那么多,还是没啥好用的。这些新特性真正存在的意义恐怕就是异步编程了。
举个ajax 请求的例子:
$.ajax({
url:"xxxxx.do",
success:function(value){
console.log(value)
}
})
如果需要嵌套,那就麻烦了:
$.ajax({
url:"xxxxx.do",
success:function(value){
$.ajax({
url:"xxxxx.do",
success:function(value){
$.ajax({
url:"xxxxx.do",
success:function(value){
console.log(value)
}})
}
})
}
})
根据以上的知识点:
-
yield 会暂停函数执行过程,等待下次next的迭代:
-
在迭代器next传入参数会被下一个next执行操作捕获;
一般来说我们会想到在业务里面使用生成器,给我们可以迭代的数据。
我们亦可以反其道行之:生成器里面写业务,由一个迭代工具(任务执行工具)帮我们迭代。不关注什么时候next。
1、简单任务执行器
requestIteratorCreator = function*(){
const result = yield 2;
yield result
}
run = function(requestIteratorCreator){
task = requestIteratorCreator();
let result = task.next();
doTask = function(){
if(!result.done){
result = task.next(result.value);
doTask()
}
}
doTask()
}
run(function*(){
const result = yield 1;//
console.log(result);//1
})
2、加入异步处理
ajaxRequest = function(url,callback){
setTimeout(function(){
callback(`成功返回${url}的数据`)
},1000);
}
//普通的异步请求:
ajaxRequest("/user/login",function(data){
console.log(data,"登陆成功!");
console.log("正在获取用户基础信息")
ajaxRequest("/user/getUserInfo",function(data){
console.log(data,"用户基础信息获取成功!");
});
});
//成功返回/user/login的数据 登陆成功!
//正在获取用户基础信息
//成功返回/user/getUserInfo的数据 用户基础信息获取成功!
如果是多次请求嵌套,那就是地狱回调了。我们可以利用yield 暂停这个特性,还有next传值来处理异步:
requestCreater = function(url){
return function(callback){
return ajaxRequest(url,callback)
}
}
run = function(requestIteratorCreator){
task = requestIteratorCreator();
result = task.next();
doTask = function(){
if(!result.done){
if(typeof result.value === 'function'){
result.value(function(data){
result = task.next(data);
doTask()
})
}else{
result = task.next(task.value);
doTask()
}
}
}
doTask()
}
run(function *(){
//写同步代码啦
const loginResult = yield requestCreater("/user/login")
console.log(loginResult)
const userInfoResult = yield requestCreater("/user/userInfo")
console.log(userInfoResult)
})
//成功返回/user/login的数据
//成功返回/user/userInfo的数据
//美滋滋啊!!!!
异步处理数据,ES6提供了更加有趣的Promise。
附录:ES2018(ES9)异步迭代
上面异步处理提到的Promise,在ES7发扬光大,然后ES9加入了异步迭代的方法。
我们先来了解下:
1、async-await
ES7的async-await
带我们走向光明
fetchRequest = function(url){
return new Promise((resolve,reject) =>{
setTimeout(function(){
resolve(`成功返回${url}的数据`)
},1000);
})
}
async function runFetchDemo() {
const loginInfo = await fetchRequest("/user/login");
console.log(loginInfo)
const userInfo = await fetchRequest("/user/userInfo");
console.log(userInfo)
}
runFetchDemo();
//成功返回/user/login的数据
//成功返回/user/userInfo的数据
2、Promise 的异步迭代
getFetchIterator = function*(){
yield fetchRequest("/user/login");
yield fetchRequest("/user/userInfo");
}
async function runFetchDemo() {
asyncIterator = getFetchIterator();
const loginInfo = await asyncIterator.next();
console.log(loginInfo)
const userInfo = await asyncIterator.next();
console.log(userInfo)
// 瞬间输出两个 padding的Promise,很明显不是我们要的
asyncIterator = getFetchIterator();
for await (const item of asyncIterator) {
console.log(item)
}
//成功返回/user/login的数据
//成功返回/user/userInfo的数据
}
是不是觉得简化了很多?
好的我讲完了。