【JavaScript】Iterator 迭代器 & Genrator 生成器

Iterator 迭代器

迭代器:迭代器是一种接口;其本质就是一个指针对象

Iterator 的作用:
  1. 为各种数据结构,提供一个简便的访问接口
  2. 使得数据结构的成员能够按某种次序排列
  3. 主要供 for … of 使用
逻辑过程:
  1. 创建一个指针对象,指向当前数据结构的起始位置
  2. 第 1 次调用指针对象的 next(),指针就指向数据结构的第 1 个元素
  3. 第 2 次调用指针对象的 next(),指针就指向数据结构的第 2 个元素
  4. 不断调用指针对象的 next(),直到它指向数据结构的结束位置

具有 Iterator 接口的数据结构:
① Array、② Map、③ Set、④ String、⑤ 函数的 arguments 类数组对象、⑥ DOM 中的 NodeList 类数组对象

数组的 Symbol.iterator 属性
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator](); // 返回一个迭代器对象
console.log(iter.next()); // { value: 'a', done: false }
console.log(iter.next()); // { value: 'b', done: false }
console.log(iter.next()); // { value: 'c', done: false }
console.log(iter.next()); // { value: undefined, done: true }
类数组的对象调用数组的迭代器 Symbol.iterator

类数组:下标作为属性名,有 length 属性的对象

let iterable = {
    0: 'a', // 属性名与下标一样
    1: 'b',
    2: 'c',
    length: 3,
    [Symbol.iterator]: Array.prototype[Symbol.iterator]
};

for (let item of iterable) {
    console.log(item); // a b c
}

注意,普通对象部署数组的迭代器 Symbol.iterator,无效果

let iterable = {
    a: 'a', // 属性名与下标是不一样的
    b: 'b',
    c: 'c',
    length: 3,
    [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
    console.log(item); // undefined undefined undefined
}
字符串的包装类是一个类数组的对象,也具有 Iterator 接口
let str = "hi";
console.log(typeof str[Symbol.iterator]); // function

let iterator = str[Symbol.iterator](); // 返回一个迭代器对象
console.log(iterator.next()); // {value: "h", done: false}
console.log(iterator.next()); // {value: "i", done: false}
console.log(iterator.next()); // {value: undefined, done: true}
封装迭代器
let arr = [1, 2];

arr.myIterator = function () {
    let i = 0;
    return {
        next: () => { // 这里使用箭头函数,才能使 this 指向 arr
            let done = i >= this.length;
            let value = done ? undefined : this[i++];
            return {
                value,
                done
            };
        };
    };
};

let obj = arr.myIterator();
console.log(obj.next()); // {value: 1, done: false}
console.log(obj.next()); // {value: 2, done: false}
console.log(obj.next()); // {value: undefined, done: true}

Generator 生成器

定义生成器

  1. 关键字 function 与函数名之间有一个星号 *
  2. 可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决⽅案
  3. Generator 函数不能和 new 一起使用,会报错
  4. 不用写 return 语句
function* test() {
    console.log("first");
    let a = yield 1;
    console.log("second");
    console.log("a", a);
    let b = yield 2;
    console.log("third");
    console.log("b", b);
}
  1. 也可以使用函数表达式的方式定义生成器:
    此时是匿名函数表达式,所以 *function 关键字和小括号之间
let test = function* () {}
  • 注意:不能通过箭头函数创建生成器,毕竟 * 都不知该写哪里了是吧
  1. 作为对象的方法:
let obj = {
    createIterator: function* (arr) {
        for (let i = 0; i < arr.length; i++) {
            yield arr[i];
        }
    }
};

也可以用 ES6 对象方法的简写方式来创建生成器。此时需要在函数名前添加一个星号 *

let obj = {
    * createIterator(arr) {
        for (let i = 0; i < arr.length; i++) {
            yield arr[i];
        }
    }
};

调用生成器

  • 该函数和普通函数不同,在执行的时候函数并不会运行,会返回⼀个分步执行对象
    该对象存在 next 方法,⽤来让程序继续执行,当程序遇到 yield 关键字时会停顿
  • next 返回的对象中包含 valuedone 两个属性:value 代表上⼀个 yield 返回的结果,done 代表程序是否执行完
// 获取分步执⾏对象
let generator = test();
console.log("generator", generator); // generator test {<suspended>}
// 步骤1 该程序从起点执⾏到第⼀个 yield 关键字后,step1 的 value 是 yield 右侧的结果 1
let step1 = generator.next(); // first
console.log(step1); // {value: 1, done: false}
//步骤2 该程序从 var a 开始执⾏到第 2 个 yield 后,step2 的 value 是 yield 右侧的结 2
let step2 = generator.next(); // second
console.log(step2); // a undefined
// 由于没有 yield 该程序从 var b 开始执⾏到结束
let step3 = generator.next(); // third    b undefined
console.log(step3); // {value: undefined, done: true}

上例中的 next 函数没有接收参数,所以 a b 的值为 undefined

这是因为在分步执行过程中,我们是可以在程序中对运行的结果进行人为干预的
也就是说 yield 返回的结果和他左侧变量的值都是可以干预的

// 获取分步执⾏对象
let generator = test();
console.log("generator", generator); // generator test {<suspended>}
// 步骤1 该程序从起点执⾏到第⼀个 yield 关键字后,step1 的 value 是 yield 右侧的结果 1
let step1 = generator.next(); // first
console.log(step1); // {value: 1, done: false}
//步骤2 该程序从 var a 开始执⾏到第 2 个 yield 后,step2 的 value 是 yield 右侧的结 2
let step2 = generator.next(step1.value); // second
console.log(step2); // a 1
// 由于没有 yield 该程序从 var b 开始执⾏到结束
let step3 = generator.next(step2.value); // third    b 2
console.log(step3); // {value: undefined, done: true}

next 方法传入参数 stepX.value 之后,a b 就能获取到正确的值了

return 语句
  • 可以写上 return,但 return 后面的语句也可以通过 next() 获取
function* show() {
    yield "a";
    yield "b";
    return 'c';
}
let g1 = show(); // 获取获取分步执⾏对象
let step1 = g1.next();
console.log("step1", step1); // step1 {value: 'a', done: false}
let step2 = g1.next();
console.log("step2", step2); // step2 {value: 'b', done: false}
let step3 = g1.next();
console.log("step3", step3); // step3 {value: 'c', done: true}
let step4 = g1.next();
console.log("step4", step4); // step4 {value: undefined, done: true}
批量处理 yield 语句
  • 如果传入数组参数,可以通过 for 循环,批量处理 yield 语句
function* show(arr) {
    for (let i = 0; i < arr.length; i++) {
        yield arr[i];
    }
}
let g = show([1, 2, 3]);
console.log(g); // show {<suspended>}
console.log(g.next()); // {value: 1, done: false}
console.log(g.next()); // {value: 2, done: false}
console.log(g.next()); // {value: 3, done: false}
console.log(g.next()); // {value: undefined, done: true}
console.log(g); // show {<closed>}
  • 注意yield 关键字只能在生成器 generator 函数的子作用域中使用,否则会报错
function* show(arr) {
    arr.forEach(function (item, index) {
        yield item; // SyntaxError: Unexpected identifier
    });
}

generator 的各种搭配

  1. for … of 循环
function* show() {
    yield "a";
    yield "b";
}
let g1 = show();

for (let val of g1) {
    console.log(val); // a b
}
  1. 解构赋值
function* show() {
    yield "a";
    yield "b";
};
let [g1, g2, g3] = show();
console.log(g1, g2, g3); // a b undefined
  1. 扩展运算符
function* show() {
    yield "a";
    yield "b";
};
let [...g1] = show();
console.log(g1); // ["a", "b"]
  1. Array.from():( Array.from 用于把类数组对象转成数组 )
function* show() {
    yield "a";
    yield "b";
};
let g1 = Array.from(show());
console.log(g1); // ["a", "b"]

可迭代对象 & for … of

  • 具有 Symbol.iterator 方法的对象,如字符串、数组、Set、Map,都默认具有迭代器
  • 通过生成器创建的,都是迭代对象
  • for … of 循环的本质就是通过每次调用迭代对象的 next(),将迭代器返回的结果对象中的 value 属性值,存到一个变量中,循环执行直到结果对象中 done 属性值为 true 为止
let arr = [10, 20, 30];
for (let num of arr) {
    console.log(num); // 10 20 30
}

异步处理

function* test() {
    let a = yield 1; // 普通数据
    console.log("a", a);
    let res = yield setTimeout(function () { // 定时器
        return 123;
    }, 1000);
    console.log("res", res);
    let res1 = yield new Promise(function (resolve) { // Promise 实例
        setTimeout(function () {
            resolve(456)
        }, 1000);
    });
    console.log("res1", res1);
}

let generator = test()
console.log("generator", generator); // generator test {<suspended>}

let step1 = generator.next();
console.log("step1", step1); // step1 {value: 1, done: false}

let step2 = generator.next(step1.value); // a 1
console.log("step2", step2); // step2 {value: 3, done: false}

let step3 = generator.next(step2.value); // res 3
console.log("step3", step3); // step3 {value: Promise, done: false}    (这里得到的 value 值是 Promise 实例)

let step4 = generator.next(step3.value); // res1 Promise {<pending>}
console.log("step4", step4); // step4 {value: undefined, done: true}

我们展开查看 Promise 对象:

value: Promise
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 456

可以发现:生成器 Generator 可以获取到普通数据、Promise 实例;但获取不了定时器内的数据

实现用 Generator 将 Promise 的异步流程同步化

我们可以通过递归调⽤的⽅式,来动态的去执⾏⼀个Generator函数,以done属性作为是否结束 的依据,通过next来推动函数执⾏,如果过程中遇到了Promise对象我们就等待Promise对象执⾏完毕再进⼊下⼀ 步,我们这⾥排除异常和对象reject的情况,封装⼀个动态执⾏的函数如下

function generatorFunctionRunner(fn) {
    // 定义分步对象
    let generator = fn();
    // 执⾏到第⼀个 yield
    let step = generator.next();
    // 定义递归函数
    function loop(stepArg, generator) {
        // 获取本次的 yield 右侧的结果
        let value = stepArg.value;
        // 判断结果是不是 Promise 对象
        if (value instanceof Promise) {
            // 如果是 Promise 对象就在 then 函数的回调中获取本次程序结果
            // 并且等待回调执⾏的时候进⼊下⼀次递归
            value.then(promiseValue => {
                if (stepArg.done == false) {
                    loop(generator.next(promiseValue), generator);
                }
            });
        } else {
            // 判断程序没有执⾏完就将本次的结果传⼊下⼀步进⼊下⼀次递归
            if (stepArg.done == false) {
                loop(generator.next(stepArg.value), generator);
            }
        }
    }
    // 执⾏动态调⽤
    loop(step, generator);
}

有了这个函数之后我们就可以调用这个函数来执行 Generator 方法了

function* test() {
    let res1 = yield new Promise(function (resolve) {
        setTimeout(function () {
            resolve('第⼀秒运⾏');
        }, 1000);
    });
    console.log(res1);

    let res2 = yield new Promise(function (resolve) {
        setTimeout(function () {
            resolve('第⼆秒运⾏');
        }, 1000);
    });
    console.log(res2);

    let res3 = yield new Promise(function (resolve) {
        setTimeout(function () {
            resolve('第三秒运⾏');
        }, 1000);
    });
    console.log(res3);
}

generatorFunctionRunner(test);
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JS.Huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值