ES6-----迭代器和生成器

一、迭代器
迭代器是一个对象,具有一些专门为迭代过程设计的专有接口。所有的迭代器对象都有一个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();
//洗手中。。。
//扒皮中。。。
//吃橘子中。。。
//洗完手--扒完皮--边吃边说橘子真好吃
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值