一、迭代器
迭代器是一个对象,具有一些专门为迭代过程设计的专有接口。所有的迭代器对象都有一个next()方法,每次调用都会返回一个结果对象。结果对象中包含两个属性:value和done;其中value是每次迭代的返回的数据,done表示迭代是否结束(Boolean)
根据上面关于迭代器的描述,我们来模拟一下:
function createIterator(items) {
var i = 0;
return {
next: function () {
var done = (i >= items.length);
var value = done ? undefined : items[i++];
return {
value,
done
}
}
}
}
var iterator = createIterator([1, 2, 3]);
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} 迭代结束
迭代器的遍历过程是这样的:
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每个迭代器都支持for-of循环
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,那么就可以认为是可迭代对象。Symbol.iterator属性必须通过中括号形式访问,其本身是一个函数,就是当前数据结构默认的迭代器生成函数。执行这个函数,就会返回一个迭代器。在ES6中,有默认的Iterator接口的数据结构有:Array、Map、Set、String、arguments、NodeList.
//Symbol.iterator
let arrIter = [1, 3, 5][Symbol.iterator]();
let map = new Map([["a", 1], ["b", 1]]);
let mapIter = map[Symbol.iterator]();
console.log(arrIter.next()); //{value: 1, done: false}
console.log(mapIter.next().value); //["a", 1]
当使用for…of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。for…of循环每执行一次都会调用可迭代对象的next()方法,并将返回的结果对象的value属性存储在一个变量中,循环将持续执行直到返回对象的done属性值为true。
let arr = [1, 2, 3];
for(let item of arr[Symbol.iterator]()){
console.log(item);
}
// 1 2 3
为了更好的访问结构中的内容,ES6为数组、Map以及Set内建了三种迭代器:
- entries() 返回一个迭代器,其值为多个键值对
- values() 返回一个迭代器,其值为集合的值
- keys() 返回一个迭代器,其值为集合中所有键名
let arr = [1, 2, 3];
let set = new Set([4, 5, 6]);
let map = new Map([["a", 7], ["b", 8], ["c", 9]]);
for (let item of arr.entries()) {
console.log(item)
}
//对于数组,其每一位的索引对应着第一个元素
//[0, 1] [1, 2] [2, 3]
for (let item of set.entries()) {
console.log(item)
}
//对于Set集合,第一个元素与第二个元素为同值
//[4, 4] [5, 5] [6, 6]
for (let item of map.entries()) {
console.log(item)
}
//对于Map集合,第一个元素是键名,第二个元素是键值
//["a", 7] ["b", 8] ["c", 9]
//values() 值为集合中的值
for (let item of arr.values()) {
console.log(item)
}
// 1 2 3
for (let item of set.values()) {
console.log(item)
}
// 4 5 6
for (let item of map.values()) {
console.log(item)
}
// 7 8 9
//keys() 值为集合中的键名
for (let item of arr.keys()) {
console.log(item)
}
//0 1 2
for (let item of set.keys()) {
console.log(item)
}
//4 5 6
for (let item of map.keys()) {
console.log(item)
}
//a b c
每个集合都有默认的迭代器,在for…of循环中如果没有显示指出,则使用默认的迭代器。数组和Set集合默认的迭代器是values()方法,Map集合默认的迭代器是entries()方法。
for (let item of arr) {
console.log(item);
}
//1 2 3
for (let item of set) {
console.log(item);
}
//4 5 6
for(let item of map){
console.log(item);
}
//["a", 7] ["b", 8] ["c", 9]
在for…of中可以使用解构赋值:
for (let [name, value] of map) {
console.log(`${name} = ${value}`)
}
//依次打印:a = 7 b = 8 c = 9
字符串的迭代器
字符串也可以通过for循环遍历,但是对于双字节字符,for循环无法遍历。而for…of循环可以
let str = 'I喜欢Orange';
for (let item of str) {
console.log(item)
}
//依次打印:I 喜 欢 O r a n g e
类数组arguments和NodeList的迭代器
//类数组的迭代
function iterateArg() {
console.log(arguments[Symbol.iterator]);
//ƒ values() { [native code] }
for (let item of arguments) {
console.log(item)
}
}
iterateArg(1, 3, 4, 5);//1 3 4 5
二、生成器
生成器是一个返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中会用到新的关键字yield
function* createIterator() {
yield 1;
yield 2;
yield 3;
}
//生成器的调用方式与普通函数相同,只不过返回的是一个迭代器
let iterator = createIterator();
console.log(iterator.next().value);//1
console.log(iterator.next().value);//2
console.log(iterator.next().value);//3
yield关键字是ES6的新特性,通过它来制定调用迭代器next()方法时的返回值以及返回顺序;每当执行完一条yield语句后函数就会自动停止执行。上面这个例子中,第一次执行next()方法执行完yield 1语句后,函数不再执行其他语句,直到再次调用next()方法。
使用yield关键字可以返回任何值或者表达式。只能在生成器内部使用。
function* createIterator(items) {
for (let i = 0, len = items.length; i < len; i++) {
yield items[i];
}
//不能使用forEach代替,yield只能在生成器中使用,普通函数中使用会报错
// items.forEach(function (item) {
// yield item;
// });
}
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next().value);//1
console.log(iterator.next().value);//2
console.log(iterator.next().value);//3
也可以通过函数表达式来创建生成器,只需要在function和小括号中间加星号(*)即可:
let createIterator = function* () {
yield 1;
}
let i = createIterator();
console.log(i.next());//{value: 1, done: false}
生成器也是函数,因此也可以通过return 语句提前退出函数执行。在生成器中,return 操作表示所有操作已经完成,属性done被设置成true;如果同时提供了相应的值,则属性value会被设置成这个值。
function* createIterator() {
yield 1;
return 3;
yield 5;
yield 7;
}
let i = createIterator();
console.log(i.next());//{value: 1, done: false}
console.log(i.next());//{value: 3, done: true}
可以在调用next()方法时传入参数,传入的参数将作为生成器内部上一条yield语句的返回值。但是第一次调用next()方法时无论传入什么参数都会被丢弃。
function* createIterator() {
let result;
result = yield 1;
console.log(result);
result = yield 2;
console.log(result);
result = yield 3;
}
let i = createIterator();
i.next('firstNext');
i.next('secondNext');
i.next('thirdNext');
//secondNext
//thirdNext
可以通过throw方法,让迭代器执行时抛出错误
function* createIterator() {
let first = yield 1;
let second = yield first + 2;
yield second + 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('error'))); //Error: error
//在let second求值前,错误就被抛出并阻止了代码的运行
function* proIterator() {
let first = yield 1;
let second;
try {
second = yield first + 2;
} catch (ex) {
second = 6;
}
yield second + 2;
}
let iter = proIterator();
console.log(iter.next()); //{value: 1, done: false}
console.log(iter.next(4)); //{value: 6, done: false} first = 4;
console.log(iter.throw(new Error('error')));
//调用throw()方法后也会像调用next()方法一样返回一个结果对象。由于在生成器内捕获了这
//个异常,因而会继续执行下一个yield语句。
//second = 6 {value: 8, done: false}
console.log(iter.next()); //{value: undefined, done: true}
委托生成器:在某些情况下,可能会需要将两个迭代器合二为一,这时可以创建一个生成器,再给yield语句添加一个星号,就可以将生成数据的过程委托给其他迭代器。当定义这个生成器的时候,需要将星号放在关键字yield和生成器函数名之间。
//委托生成器
function* createNum() {
yield 1;
yield 2;
}
function* createColor() {
yield 'red';
yield 'yellow';
}
function* createIterator() {
yield* createNum();
yield* createColor();
}
let iter = createIterator();
console.log(iter.next());//{value: 1, done: false}
console.log(iter.next());//{value: 2, done: false}
console.log(iter.next());//{value: "red", done: false}
console.log(iter.next());//{value: "yellow", done: false}
console.log(iter.next());//{value: undefined, done: true}
也可以将生成器和迭代器应用于异步操作中
function wash() {
console.log('洗手中。。。');
setTimeout(() => process.next('洗完手'), 2000);//用setTimeout模拟异步
}
function peel() {
console.log('扒皮中。。。');
setTimeout(() => process.next('扒完皮'), 2000);
}
function eat() {
console.log('吃橘子中。。。');
setTimeout(() => process.next('边吃边说橘子真好吃'), 3000)
}
function* processIterator() {
let result1, result2, result3;
result1 = yield wash();
result2 = yield peel();
result3 = yield eat();
console.log(`${result1}--${result2}--${result3}`);
return true;
}
let process = processIterator();
process.next();
//洗手中。。。
//扒皮中。。。
//吃橘子中。。。
//洗完手--扒完皮--边吃边说橘子真好吃