欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Iterator 遍历器的简单使用

程序员文章站 2024-03-22 16:18:46
...

Object接口 实现Iterator

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

没有实现Iterator 接口的Object


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

let arr = [...obj];

结果:

Iterator 遍历器的简单使用


实现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]

结果:
Iterator 遍历器的简单使用


模拟 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
Iterator 遍历器的简单使用

无return

Iterator 遍历器的简单使用

在 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 实例,代码看起来像同步,执行其实是异步,不用自己手动进行下一次遍历.