第八章、迭代器和生成器
1、什么是迭代器
迭代器是一种特殊对象,所有的迭代器对象都有一个next()方法,每次调用都返回一个结果对象;
结果对象有两个属性:value表示下一个将要返回的值/undefined,done一个布尔类型的值,表示没有可返回数据时返回true;
迭代器还会保存一个内部指针,用来指向当前集合中值得位置。
2、什么是生成器
生成器是一种返回迭代器的函数,通过function关键字后的*开表示,函数中会用到新的关键字yield;
星号可以紧挨着function关键字,也可以中间空一个空格。
每当执行完一条yield之后,函数就会自动停止。
使用yield关键字可以返回任何值或表达式,因此可以通过生成器批量的给迭代器添加元素,比如在循环中使用yield关键字。
注:不能用箭头函数创建生成器!
1、yield的使用限制:
yield关键字只能在生成器内部使用,不能在其他地方使用,即便在生成器内部嵌套的函数内也不能使用,否则抛出语法错误。
yield关键字和return关键字一样,都不能穿透函数边界。
2、生成器对象的方法
let o = {
*createIterator(items){
for( let i=0; i<items.length; i++){
yield items[i];
}
}
}
let iterator = o.createIterator([1,2,3]);
3、可迭代对象和for-of循环
可迭代对象具有Symbol.iterator属性,Symbol.iterator通过指定的函数返回一个作用于附属对象的迭代器。
ES6中,所有的集合对象和字符串都是可迭代对象,这些对象中都有默认的迭代器。
生成器默认会为Symbol.iterator属性赋值,所以通过生成器创建的迭代器都是可迭代对象。
for-of循环每执行一次内部就会调用一次可迭代对象的next()方法;
for-of不能用于不可迭代对象、null或undefined;
1、访问默认迭代器Symbol.iterator
let values = [1,2,3];
let iterator = values[Symbol.iterator]();
console.log(iterator.next()); //"{value:1,done:false}"
console.log(iterator.next()); //"{value:2,done:false}"
console.log(iterator.next()); //"{value:3,done:false}"
console.log(iterator.next()); //"{value:undefined,done:true}"
事实上,for-of内部便做了上述工作
由于具有Symbol.iterator的对象都有默认的迭代器,因此可以用它来检测对象是否为可迭代对象。
function isIterator(object){
return typeof object[Symbol.iterator] === "function";
}
2、创建可迭代对象
let collection = {
items = [1,2.3],
*[Symbol.iterator](){
for (let item of this.items){
yield item;
}
}
};
for (let i of collection){
condole.log(i);
}
4、内建迭代器
1、集合对象迭代器
ES6中的集合对象(数组、Map集合、Set集合)都内建了一下三种迭代器:
entries():返回一个迭代器,其值为多个键值对。
values():返回一个迭代器,其值为集合中的值。
keys():返回一个迭代器,其值为集合中的所有键名。
每个集合类型都有一个默认的迭代器,在for-of循环中,如果没有显式指定则使用默认的迭代器。
数组和Set集合的默认迭代器是values()方法,Map集合的默认迭代器是entries()方法。
可以在for-of循环中使用解构语法,可以更方便的取值。
let data = new Map();
data.set("title","es6");
data.set("format","book");
for (let [key,value] of data){
console.log(`${key}---${value}`);
}
2、字符串迭代器
ES5已经支持方括号语法访问字符串,但是方括号操作的是编码单元而非字符,因此无法正确的访问双字节字符。
ES6已经支持Unicode,可以通过for-of循环访问正确的内容。
3、NodeList迭代器
ES6中DOM定义中的NodeList类型也拥有了默认迭代器,其行为与数组的默认迭代器完全一致,可以用for-of循环遍历。
5、展开运算符与非数组可迭代对象
展开运算符可以作用于任意可迭代对象。
6、高级迭代器功能
1、给迭代器传递参数
可以用迭代器的next()方法返回值,也可以在生成器内部使用yield关键字来生成值。
如果给迭代器的next()方法传递参数,则这个参数的值就会替代生成器内部上一条yield语句的返回值。
第一次调用next()方法前不会执行任何yield语句,所以第一次调用next()方法时传递参数是毫无意义的。
function *createItearator(){
let first = yield 1;
let second = yield first + 2; // 4+2
yield second + 3; // 5+3
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next(4)); // "{ value: 6, done: false}"
console.log(iterator.next(5)); // "{ value: 8, done: false}"
console.log(iterator.next()); // "{ value: undefined, done: true}"
2、在迭代器中抛出错误
可以给迭代器传递错误条件,通过throw()方法,将错误对象传递给throw()方法后,在迭代器继续执行时会被抛出。
因此可以在生成器内部通过try-catch代码块来捕获错误。
function *createItearator(){
let first = yield 1;
let second;
try {
second = yield first + 2; // 4+2
}catch{
second = 6;
}
yield second + 3; // 6+3
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next(4)); // "{ value: 6, done: false}"
console.log(iterator.throw(new Error("boom"))); // "{ value: 9, done: false}"
console.log(iterator.next()); // "{ value: undefined, done: true}"
在迭代器内部,如果使用了yield语句,则可以通过next()方法和throw()方法控制执行过程。
3、生成器返回语句
生成器中return表示所有操作已经完成,done属性被设置为true,返回的指定值被赋值给value属性。
function *createIterator(){
yield 1;
return 43;
}
let iterator = createIterator();
console.log(iterator.next()); // "{value: 1, done: false}"
console.log(iterator.next()); // "{value: 43, done: true}"
console.log(iterator.next()); // "{value: undefined, done: true}"
通过return语句指定的返回值,只会在返回对象中出现一次。
备注:展开运算符和for-of循环语句会直接忽略通过return语句返回的任何指定值,只要done一变为true就立即停止读取其他的值。
4、委托生成器
将两个迭代器合二为一,创建一个生成器,再给yield语句添加一个*号,就可以将生成数据的过程委托给其他迭代器。
function *createNumberIterator(){
yield 1;
yield 2;
return 3;
}
function *createRepeatingIterator(count){
for (let i=0;i<count;i++){
yield "repeat";
}
}
function *createCombinedIterator(){
let result = yield *createNumberIterator();
yield result;
yield *createRepeatingIterator(result);
}
let iterator = createCombinedIterator();
console.log(iterator.next()); // "{vlaue:1, done:false}"
console.log(iterator.next()); // "{vlaue:2, done:false}"
console.log(iterator.next()); // "{vlaue:3, done:false}"
console.log(iterator.next()); // "{vlaue:"repeat", done:false}"
console.log(iterator.next()); // "{vlaue:"repeat", done:false}"
console.log(iterator.next()); // "{vlaue:"repeat", done:false}"
console.log(iterator.next()); // "{vlaue:undefined, done:true}"
备注:yield *也可直接用于字符串,例如yield * "hello",此时将使用字符串的默认迭代器values()
7、异步任务执行
执行异步操作的传统方式是调用一个函数并传入相应的回调函数,这种方式在执行的任务很少的时候比较有效。
如果执行的任务很多或者回调函数中需要嵌套回调函数的时候就显得比较糟糕了。这时候,生成器和yield就比较有用了。
1、简单任务执行器
2、向任务执行器传递数据
3、异步任务执行器
function run(taskDef){
//创建一个无限使用的迭代器
let task = taskDef();
//开始执行任务
let result = task.next();
// 循环调用next()函数
function step(){
// 如果任务没有执行完成,则继续执行
if (!result.done){
if (typeof result.value === "function"){
result.value(function(error, data){
if (error){
result = task.throw(error);
return ;
}
result = task.next(data);
step();
});
} else {
result = task.next(result.value);
step();
}
}
}
// 开始迭代执行
step();
}
let fs = require("fs");
function readFile(filename){
return function(callback){
fs.readFile(filename,callback);
};
}
run(function *(){
let content = yield readFile("config.json");
doSomeThingWith(contents);
});
利用yield实现的异步文件读取。