1. 90前端首页
  2. 前端开发
  3. JavaScript

es6之迭代器、生成器

迭代器(iterator)

简单来说,迭代器(iterator)就是一个满足一定规则的对象。

规则:如果一个对象中含有一个next方法,next方法中返回一个对象,其中中包含两个属性valuedone,那么这个对象就是一个迭代器。

如下代码就是一个迭代器:

//生成随机数的迭代器
let iterator = {
    total: 2,
    i: 1,
    next(){
        let obj = {
            value: this.i > this.total ? undefined : Math.random(), //下一个数据的值
            done: this.i > this.total, //是否还有下一个数据
        }
        this.i++;
        return obj;
    }
}

执行方式,调用next函数

console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 2, done: false}
console.log(iterator.next()) //{value: undefined, done: true}

遍历迭代器数据

let next = iterator.next();

while(!next.done){
    console.log(next.value);
    next = iterator.next();
}

迭代器创建函数(iterator creator)

一个返回迭代器对象的函数。

function createIterator(total = 2){
    let i = 1;
    return {
        next(){
            let obj = {
                value: i > total ? undefined : Math.random(), //下一个数据的值
                done: i > total, //是否还有下一个数据
            }
            i++;
            return obj;
        }
    }
}

const iterator = createIterator(5)

将一个数组改成迭代模式:

function createArrIterator(arr){
    let i = 0;
    return {
        next(){
            let obj = {
                value: arr[i++],
                done: i > arr.length
            }
            return obj;
        }
    }
}

const iterator = createArrIterator([3, 4,5, 23, 4, 6, 34]);

迭代协议

只有满足迭代协议的对象才能使用for of遍历,否则就会报错。

const obj = {}

for(const i of obj){
    console.log(i) // \"Uncaught TypeError: obj is not iterable\"
}

如果想使用for of遍历obj,只要将obj改造成满足可迭代协议的对象即可。

迭代协议:满足可迭代协议的对象要有一个符号属性(Symbol.iterator),属性的值是一个无参的迭代器创建函数。如下所示:

//obj加入[Symbol.iterator]属性后可被迭代
const obj = {
    [Symbol.iterator]: (total = 2) => { //迭代器创建函数
        let i = 1;
        return {
            next() {
                let obj = {
                    value: i > total ? undefined : Math.random(), //下一个数据的值
                    done: i > total, //是否还有下一个数据
                }
                i++;
                return obj;
            }
        }
    }
}

for(const i of obj){
    console.log(i); // 不报错
}

ArrayMapSet可以使用for of遍历,是因为他们的原型上都有符号属性(Symbol.iterator)

for of执行原理

实际上for of遍历可迭代对象时,就是运行该对象上的Symbol.iterator属性,然后调用迭代器的next方法直到done属性是true位置。

const obj = {
    [Symbol.iterator]: (total = 2) => {
        let i = 1;
        return {
            next() {
                let obj = {
                    value: i > total ? undefined : Math.random(), //下一个数据的值
                    done: i > total, //是否还有下一个数据
                }
                i++;
                return obj;
            }
        }
    }
}


for(const i of obj){
    console.log(i); // 不报错, i实际上就是迭代器next方法返回对象里面的value属性
}

//for of 代码相当于如下代码:
const iterator = obj[Symbol.iterator]();
let item = iterator.next();
while(!item.done){
    const i = item.value;
    console.log(i) //执行for of中的代码
    item = iterator.next()
}

生成器 generator

由构造函数Generator创建的对象,创建的对象是一个迭代器,也满足可迭代协议。

Generator构造函数是浏览器内置的,开发者无法使用。

//伪代码
const generator = new Generator();

//generator的内部大概是这样的:
generator = {
    next(){...}, //具有next函数
    [Symbol.iterator](){...}, //具有(Symbol.iterator)属性
}

生成器创建函数(generator function)

开发者无法直接调用Generator创建生成器,只能使用生成器创建函数创建一个生成器。

生成器函数定义:只要function关键字和函数名之间加入一个*,该函数就是生成器函数,会返回一个生成器。

function *createGenerator(){}

const generator = createGenerator(); //得到一个生成器

console.log(\"next\" in generator); //true
console.log(Symbol.iterator in generator); //true
console.log(generator.next === generator[Symbol.iterator]().next); //true

yield关键字

写在生成器内部,相当于暂停,配合next方法可以在生成器外部控制生成器函数内部代码的执行。

  1. yield关键字只能在生成器内部使用;
  2. 每次调用生成器的next方法后,代码会从上一个yield执行到下一个yield
  3. 如果没有生成器函数内部没有yield关键字,则里面的代码一行都不会执行;
  4. return返回的值作为next方法的done第一次为true时的value值;
  5. yield关键字后面的值会当做执行next方法时返回的value值;
  6. yield关键字表达式返回的值等于执行next方法时传入的参数。

举个例子:大家最好使用浏览器,手动调用generator.next(),一步一步看比较容易理解。

function* createGenerator() {
    let res;
    console.log(\"开始\", res);
    
    res = yield 1; //将第2个next传入的参数作为返回值赋值给res

    console.log(\"打印1\", res);
    
    res = yield 2; //将第3个next传入的参数作为返回值赋值给res
    
    console.log(\"打印2\", res);
    
    return \"结束\"
}

const generator = createGenerator();
let result = generator.next();
console.log(\"调用第1次next的返回值:\", result);

result = generator.next(result.value);
console.log(\"调用第2次next的返回值:\", result);

result = generator.next(result.value);
console.log(\"调用第3次next的返回值:\", result);

result = generator.next(result.value);
console.log(\"调用第4次next的返回值:\", result);

//输出:
开始 undefined
调用第1次next: { value: 1, done: false }
打印1 1
调用第2次next: { value: 2, done: false }
打印2 2
调用第3次next: { value: \'结束\', done: true }
调用第4次next: { value: undefined, done: true }
利用生成器函数将异步代码转化为同步代码:
/**
 * 模拟一个请求
 */
function getData() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(\'数据\');
        }, 2000)
    })
}

/**
 * 生成器函数,在这里写业务逻辑,可以将异步代码的写法转化为同步代码写法
 */
function* task() {
    console.log(\'获取数据中...\');
    let result = yield getData(); //将异步代码转化为同步的写法
    console.log(\'得到数据:\', result);
    //对数据进行后续处理...
}

/**
 * 运行生成器的通用函数
 */
function run(generatorFunc) {
    const generator = generatorFunc();
    next();

    function next(nextValue) {
        let result = generator.next(nextValue)
        if (result.done) { //迭代结束
            return;
        } else {
            const value = result.value;
            if (value instanceof Promise) {
                value.then(data => next(data));
            } else {
                next(value);
            }
        }
    }
}

run(task); //执行生成器函数代码

//输出:
获取数据中...
得到数据: 数据

本文来自网络整理,转载请注明原出处:https://segmentfault.com/a/1190000021796786

展开阅读全文

发表评论

登录后才能评论