Iterator 遍历器的简单使用

Object接口 实现Iterator

我们可以使用 ES6 的展开运算符 … 和 for…of… 去遍历带有 Iterator 接口的数据结构,需要注意的是,Object 本身不具备 Iterator 接口,所以我们无法通过 … 把一个对象扩展到一个数组中,并且会报错,我们可以通过代码手动将 Object 类型实现 Iterator 接口。

没有实现Iterator 接口的Object


let obj = {
    a: 1,
    b: 2,
    c: 3
};

let arr = [...obj];

结果:

这里写图片描述


实现Iterator 接口的Object

// 通过 Generator 函数给 Object 扩展 Iterator 接口
Object.prototype[Symbol.iterator] = function*() {
    for (var key in this) {
        yield this[key];
    }
};

// 测试 Iterator 接口
let obj = {
    a: 1,
    b: 2,
    c: 3
};

let arr = [...obj];

console.log(arr); // [1, 2, 3]

结果:
这里写图片描述


模拟 Generator

Generator 函数是一个生成器,调用后会返回给我们一个 Iterator 遍历器对象,在对象中有一个 next 方法,调用一次 next,帮我遍历一次,返回值为一个对象,内部有 value 和 done 两个属性,value 属性代表当前遍历的值,done 属性代表是否遍历完成,如果遍历完成后继续调用 next,返回的对象中 value 属性值为 undefined,done 属性值为 true,这个遍历器在进行数据遍历时更像给我们提供了一个暂停功能,每次都需要手动调用 next 去进行下一次遍历。

// 模拟遍历器生成函数
function iterator(arr) {
    var i = 0;

    return {
        next: function() {
            var done = i >= arr.length;
            var value = !done ? arr[i++] : undefined;

            return {
                value: value,
                done: done
            };
        }
    };
}

测试一下模拟的遍历器生成函数:

// 测试 iterator 函数
var arr = [1, 3, 5];

// 遍历器
var result = iterator(arr);

result.next(); // {value: 1, done: false}
result.next(); // {value: 3, done: false}
result.next(); // {value: 5, done: false}
result.next(); // {value: undefined, done: true}

Generator 的基本使用

在普通的函数 function 关键字后加一个 * 就代表声明了一个生成器函数,执行后返回一个遍历器对象,每次调用遍历器的 next 方法时,遇到 yield 关键字暂停执行,并将 yield 关键字后面的值会作为返回对象中 value 的值,如果函数有返回值,会把返回值作为调用 next 方法进行遍历完成后返回的对象中 value 的值,果已经遍历完成,再次 next 调用这个 value 的值会变成 undefined。

// 生成器函数
function* gen() {
    yield 1;
    yield 2;
    return 3;
}

// 遍历器
let it = gen();

it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: true}
it.next(); // {value: undefined, done: true}

观察两个结果:

return
这里写图片描述

无return

这里写图片描述

在 Generator 函数中可以使用变量接收 yield 关键字执行后的返回值,只是接收的值并不是 yield 关键字后面表达式执行的结果,而是遍历器在下一次调用 next 方法时传入的参数。

在 Generator 函数中,如果在其他函数或方法调用的回调内部(函数的执行上/下文发生变化)不能直接使用 yield 关键字。

/ 正确的写法
function* gen(arr) {
    for(let i = 0; i < arr.length; i++) {
        yield arr[i];
    }
}

如果在一个 Generator 函数中调用了另一个 Generator 函数,在调用外层函数返回遍历器的 next 方法时是不会遍历内部函数返回的遍历器的。

// 外层的生成器函数
function* genOut() {
    yield "a";
    yield genIn();
    yield "c";
}

// 内层的生成器函数
function* genIn() {
    yield "b";
}

// 遍历器
let it = genOut();

it.next(); // {value: 'a', done: false}
it.next(); // 返回 genIn 的遍历器对象
it.next(); // {value: 'c', done: false}
it.next(); // {value: undefined, done: true}

在 genOut 返回的遍历器调用 next 遇到 yield* 表达式时帮我们去遍历了 genIn 返回的遍历器,其实 yield* 内部做了处理,等同于下面代码:

// 外层的生成器
function* genOut() {
    yield "a";
    for (let v of genIn()) {
        yield v;
    }
    yield "c";
}

// 内层的生成器
function* genIn() {
    yield "b";
}

// 遍历器
let it = genOut();

it.next(); // {value: 'a', done: false}
it.next(); // {value: 'b', done: false}
it.next(); // {value: 'c', done: false}
it.next(); // {value: undefined, done: true}

Generators 与 Promise 结合

因为 Generator 函数在执行时遇到 yield 关键字会暂停执行,那么 yield 后面可以是异步操作的代码,比如 Promise,需要继续执行,就手动调用返回遍历器的 next 方法,因为中间有一个等待的过程,所以在执行异步代码的时候避免了回调函数的嵌套,在写法上更像同步,更容易理解。

我们来设计一个 Generator 函数与 Promise 异步操作结合的使用场景,假设我们需要使用 NodeJS 的 fs 模块读取一个文件 a.txt 的内容,而 a.txt 的内容是另一个需要读取文件 b.txt 的文件名,读取 b.txt 最后打印读取到的内容 “Hello world”。

// 引入依赖
let fs = require("fs");
let util = require("util");

// 将 readFile 方法转换成 Promise
let read = util.promisify(fs.readFile);

// 生成器函数
function* gen() {
    let aData = yield read("1.txt", "utf8");
    let bData = yield read(aData, "utf8");
    return bData;
}

// 遍历器
let it = gen();

it.next().value.then(data => {
    it.next(data).then(data => {
        console.log(data); // Hello world
    });
});

co 库的使用

co 库是基于 Generator 和 Promise 的,这个库能帮我们实现自动调用 Generator 函数返回遍历器的 next 方法,并执行 yield 后面 Promise 实例的 then 方法,所以每次 yield 后面的异步操作返回的必须是一个 Promise 实例,代码看起来像同步,执行其实是异步,不用自己手动进行下一次遍历.

发布了386 篇原创文章 · 获赞 275 · 访问量 126万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 撸撸猫 设计师: 设计师小姐姐

分享到微信朋友圈

×

扫一扫,手机浏览