迭代器和产生器
迭代器和产生器是ES6很重要的两个新特性,迭代器是继承了某个特定接口的对象;产生器则是一类特殊的函数,调用产生器可以返回一个迭代器。下面我们来探讨一下这两个新特性:
- 什么是迭代器,什么是产生器;
- 可迭代(iterable)和 for...of;
- 内置迭代器类型:数组,Maps, Sets, 字符串, NodeList;
- 高级迭代器功能;
- 迭代与异步编程(Asynchonous programming);
一.什么是迭代器,什么是产生器
1.迭代器 iterators
迭代器是继承了特定用于迭代的接口的对象。所有的迭代器都有一个next()方法, 返回一个对象, 包含2个属性: value, done。
ES5创建一个迭代器函数:返回一个对象,对象包含next方法,调用next方法返回一个对象,包含value, done属性
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
var it = createIterator([1, 2, 3]);
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: undefined, done: true}
2.产生器 generators
1.产生器是一个返回迭代器的函数(Generator is a function that return an iterator),产生器可以用yield来控制函数的流程,停止或继续函数,使用function * 函数名()来表示,不能使用箭头函数表示产生器。
function *createGenerator() {
yield 1;
yield 2;
yield 3;
}
// 调用产生器返回一个迭代器
let it = createGenerator();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: undefined, done: true}
产生器最有意思的地方就是,每次yield语句执行之后就停止执行,要继续执行,需要调用迭代器的next()方法; 产生器的本质是函数(function),javascript中其余函数能用到的地方,产生器也能使用。
2.Generator function expression
let createGenerator = function*(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
};
3.yield关键词只能用于产生器内部,用在产生器内部的函数会抛出语法错误:
function *createGenerator(items) {
items.forEach(function(item) {
// 语法错误
yield item + 1;
});
}
// yield 不能跨函数边界
二.可迭代和for...of
什么类型的数据是可迭代的? 在介绍Symbol对象时, 我们知道一个对象要可迭代,则需要包含[Symbol.iterator]()属性。所有的集合对象(Array, Maps, Sets)和字符串都包含一个默认的迭代器,可以使用ES6新方法for...of.
1.创建可迭代对象
方法1:使用[Symbol.iterator]方法
class Log {
constructor() {
this.messages = [];
}
add(message) {
this.messages.push({message, timestamp: Date.now()});
}
// 实现[Symbol.iterator]属性
[Symbol.iterator]() {
let i = 0;
const messages = this.messages;
return {
next() {
if (i > messages.length) {
return {value: undefined, done: true};
}
return {value: messages[i++], done: false};
}
};
}
}
var log = new Log();
log.add("hello");
log.add("world");
// 对log实例进行迭代
for (let entry of log) {
console.log(entry);
}
// 输出结果为:
Object {message: "hello", timestamp: 1474192362106}
Object {message: "world", timestamp: 1474192369195}
方法2:使用 *[Symbol.iterator]迭代器
let collection = {
items: [],
*[Symbol.iterator]() {
for (let item of items) {
yield item;
}
}
};
collection.items.push("a");
collection.items.push("b");
for (let x of collection) {
console.log(x);
}
// "a"
// "b"
2.访问默认迭代器
var arr = [1, 2, 4];
var it = arr[Symbol.iterator]();
it.next(); // {value: 1, done: false}
// ...
3.可迭代检测
可以通过一个对象是否包含[Symbol.iterator]函数来判断一个对象是否可迭代
function isIterable(obj) {
return typeof obj[Symbol.iterator] === "function";
}
isIterable(new Set()); // true
isIterable("hello"); // true
isIterable(new WeakMap()); // false
三.内置迭代器
1.ES6有3个集合类型: Arrays, Sets, Maps, ES6也内置了3个迭代器,方便对内容进行导航:
- keys(): 返回集合中的keys;
- values(): 返回集合中的values, 这个也是Sets, Arrays的在for...of中的默认类型;
- entries(): 返回集合中的键值对, 这个是Maps在for...of中的默认类型; 对于Sets键keys和值values一样; 对于Arrays, 键为数字索引
let colors = ["red", "yellow", "blue"];
let set = new Set([1, 2, 3]);
let map = new Map([["name", "james"], ["age", 26]]);
for (let key of colors.keys()) {
console.log(key); // 0 1 2
}
// 数组默认情况
for (let value of colors.values()) {
console.log(value); // "red" "yellow" "blue"
}
// 等价于
for (let value of colors) {
console.log(value); // "red" "yellow" "blue"
}
for (let entry of colors.entries()) {
console.log(entry); // [0, "red"] [1, "yellow"] [2, "blue"]
}
for (let key of set.keys()) {
console.log(key); // 1 2 3
}
// Sets默认情况
for (let value of set.values()) {
console.log(value); // 1 2 3
}
for (let entry of set.entries()) {
console.log(entry); // [1, 1] [2, 2] [3, 3]
}
for (let key of colors.keys()) {
console.log(key); // "name" "age"
}
for (let value of colors.values()) {
console.log(value); // "james" 26
}
// Maps默认情况
for (let entry of colors.entries()) {
console.log(entry); // ["name", "james"] ["age", 26]
}
2.entries()作为Maps默认的迭代器, 可以使用在for...of中使用解构
let data = new Map();
data.set("name", "james");
data.set("age", 26);
// 使用解构
for (let [key, value] of data) {
console.log(key + "=" + value);
}
// name=james
// age=26
3.字符串使用迭代器
我们知道字符串本质上是字符的集合,字符串相当字符组成的数组
var s = "CHARS";
for (let c of s) {
console.log(c);
}
// C H A R S
4.spread操作符
spread操作符可以作用于所有可迭代集合,使用集合默认的迭代器,比如Maps默认的迭代器为entries
let map = new Map([["name", "james"], ["age", 26]]);
var arr = [...map];
console.log(arr); // [["name", "james"], ["age", 26]]
四.高级迭代功能
1传递参数给迭代器
我们知道产生器调用返回迭代器, 迭代器遇见yield语句函数停止执行,等待下一次调用next(),我们可以传入参数到next(),这种功能是异步编程的基础(异步编程中会给示例)
function *createIterator() {
let first = yield 1;
let second = yield first + 3;
yield second + 1;
}
var it = createIterator(); // 产生迭代器
it.next(); // 执行"yield 1" 并且函数停止 {value: 1, done: false}
it.next(5); // 执行let first = 5赋值 并且执行到 "yield first + 3" 然后停止
// {value: 8, done: false}
it.next(10); // 执行let second = 10赋值 并执行"yield second + 1" 然后停止
// {value: 11, done: false}
it.next(); // {value: undefined, done: true}
2.在迭代器中抛出错误 iterator.throw()
迭代器能够实现一个throw()方法,显示迭代器抛出错误,这对于异步编程很有用
function *createIterator() {
let first = yield 1;
let second = yield first + 3; // yield 4 + 3, then throw
yield second + 1; // 不再执行
}
let iterator = createIterator();
iterator.next(); // {value: 1, done: false}
iterator.next(4); // {value: 7, done: false}
iterator.throw(new Error("boom")); // 抛出错误,下面next()不再执行
可以使用 "try...catch"对错误进行处理
function *createIterator() {
let first = yield 1;
let second;
try {
second = yield first + 3;
} catch(ex) {
second = 2; // 出错之后处理
}
yield second + 1;
}
let iterator = createIterator();
iterator.next(); // {value: 1, done: false}
iterator.next(4); // {value: 7, done: false}
iterator.throw(new Error("boom")); // {value: 3, done: false}
iterator.next(); // {value: undefined, done: true}
3.产生器中的return语句
在产生器中,return表示所有处理完成done: true
function *createIterator() {
yield 1;
return;
yield 2;
}
var it = createIterator();
it.next(); // {value: 1, done: false}
it.next(); // {value: undefined, done: true}
// 后面的yield语句不再能够执行
可以给return指定一个返回值
function *createIterator() {
yield 1;
return 42;
yield 12;
}
var it = createIterator();
it.next(); // {value: 1, done: false}
it.next(); // {value: 42, done: true}
it.next(); // {value: undefined, done: true}
it.next(); // {value: undefined, done: true}
4.委托产生器(delegate generators)
1.将2个迭代器结合成1个
function *createNumIterator() {
yield 1;
yield 2;
}
function *createColorIterator() {
yield "red";
yield "blue";
}
// 合为一个
function *combinedIterator() {
yield *createNumIterator();
yield *createColorIterator();
yield true;
}
var it = combinedIterator();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: "red", done: false}
it.next(); // {value: "blue", done: false}
it.next(); // {value: true, done: false}
it.next(); // {value: undefined, done: true}
2.进一步使用产生器返回的值,可以完成复杂的任务
function *createNumIterator() {
yield 1;
yield 2;
return 3; // 利用这个返回值
}
function *createRepeatIterator(count) {
for (let i = 0; i < count; i++) {
yield "repeat";
}
}
//合成1个
function *combinedIterator() {
let result = yield *createNumIterator(); // 将返回的值赋给变量result
yield *createRepeatIterator(result); // 将上面赋值的变量传入另一个产生器
}
var it = createRepeatIterator();
// 注意返回的3作为变量传入下个迭代器, 没有显示result
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: undefined, done: true}
// 若想显示result
function *combinedIterator() {
let result = yield *createNumIterator(); // 将返回的值赋给变量result
yield result; // 将result也产出
yield *createRepeatIterator(result); // 将上面赋值的变量传入另一个产生器
}
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: "repeat", done: false}
it.next(); // {value: undefined, done: true}
五.异步编程
迭代器和产生器可以用于异步编程,控制函数流程,因为generator允许我们在代码执行过程中暂停代码,一般使用异步编程的有异步回调函数。
1.因为yield停止执行,等待下一次next()调用,将产生器当作参数
function run(taskDef) {
// 创建迭代器, taskDef为产生器
let task = taskDef();
// 开始task, result为返回的对象
let result = task.next();
// 递归函数一直调用 next()
function step() {
// if there's no more to do
if (!result.done) {
result = task.next();
step(); // 递归调用
}
}
// 开始处理
step();
}
实现run(),传入一个generator,包含多个yield
run(function*() {
console.log(1);
yield;
console.log(2);
yield;
console.log(3);
});
2.包含数据的异步调用
其原理就是利用next()传入参数,generator中yield可以有表达式赋值操作
function run(taskDef) {
// 创建iterator
var task = taskDef();
// 开始任务
var result = task.next();
// 递归调用next()
function step() {
if (!result.done) {
result = task.next(result.value); // 传入参数
step();
}
}
// 开始处理
step();
}
调用run()
run(function *() {
let value = yield 1;
console.log(value); // 1
value = yield value + 3;
console.log(value); // 4
})
异步取回数据示例:
假设有3个json文件在 "data"目录下, 分别是tweets.json, friends.json, videos.json,包含一些数据,现在异步分别取回这些数据。
function genWrap(myGen) {
// 创建迭代器
let it = myGen();
// 开始任务
let result = it.next();
// 递归调用next()
function handle() {
if (!result.done) {
result = it.next(result.value); // 传入参数,将产出的值赋给变量
// 比如此例将$.get("data/tweets.json")赋给tweets变量
handle(); // 递归调用
}
}
// 开始处理
handle();
}
// 开始任务,调用genWrap()
genWrap(function *() {
let tweets = yield $.get("data/tweets.json"); // 使用jquery的$.get() ajax方法
console.log(tweets);
let friends = yield $.get("data/friends.json");
console.log(friends);
let videos = yield $.get("data/videos.json");
console.log(videos);
});
总结
迭代器和产生器极大的对可迭代数据结构操作提供了极大的便利,同时对异步编程来说十分的重要,因为generator和iterator对函数提供了流程控制。