彻底理解迭代器/生成器

迭代器(Iterator

定义

迭代器是 JavaScript一种用于遍历数据集合的机制,提供了一种标准的接口,使得可以按需逐个访问集合中的元素,而不需要知道集合的内部结构

要求

JavaScript中,迭代器是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol),迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式

JavaScript中这个标准就是一个特定的next方法,next方法有如下的要求

  • next是一个无参数或者一个参数的函数,返回一个拥有以下两个属性的对象

  • done(boolean)

    • 如果迭代器可以产生序列中的下一个值,则为 false(这等价于没有指定 done 这个属性)
    • 如果迭代器已将序列迭代完毕,则为 true,这种情况下 value 是可选的,有value则为迭代结束之后的默认返回值
  • value

    • 迭代器返回的任何 JavaScript 值,donetrue 时可省略
    // 简单的迭代器
    const iterator = {
      next() {
        return {
          done: false,
          value: "abc",
        };
      },
    };
    console.log(iterator.next()); // {done: false, value: 'abc'}
    
    // 练习数组的迭代器 后面会用迭代器替换
    const names = ["abc", "def", "ghi"];
    let index = 0;
    const namesIterator = {
      next() {
        if (index < names.length) {
          return {
            done: false,
            value: names[index++], // i++:先返回当前值再自增,++i:先自增,再返回新值
          };
        } else {
          return {
            done: true,
          };
        }
      },
    };
    console.log(namesIterator.next()); // {done: false, value: 'abc'}
    console.log(namesIterator.next()); // {done: false, value: 'def'}
    console.log(namesIterator.next()); // {done: false, value: 'ghi'}
    console.log(namesIterator.next()); // {done: true}
    
    // 函数封装 后面会用迭代器替换
    function createArrIterator(arr) {
      let index = 0;
      // 返回迭代器对象
      return {
        next() {
          if (index < arr.length) {
            return {
              done: false,
              vlaue: arr[index++],
            };
          } else {
            return { done: true };
          }
        },
      };
    }
    const fnNamesIterator = createArrIterator(names);
    console.log(fnNamesIterator.next()); // {done: false, value: 'abc'}
    console.log(fnNamesIterator.next()); // {done: false, value: 'def'}
    console.log(fnNamesIterator.next()); // {done: false, value: 'ghi'}
    console.log(fnNamesIterator.next()); // {done: true}
    

可迭代对象

什么是可迭代对象?它和迭代器是不同的概念:

  • 一个对象实现了iterable protocol协议时,它就是一个可迭代对象

  • 这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性

我们转成可迭代对象有什么好处?

  • 当一个对象变成一个可迭代对象的时候,就可以进行某些迭代操作

  • 比如 for...of 操作时,其实就会调用它的 @@iterator 方法

// 普通对象为不可迭代对象
// const obj = {
//   name: "obj1",
//   age: 18,
//   actions: ["run", "move", "jump"],
// };
// for (const item of obj) { // 报错:Uncaught TypeError: obj is not iterable
//   console.log(item);
// }

// 可迭代对象
const obj1 = {
  name: "obj1",
  age: 18,
  actions: ["run", "move", "jump"],
  [Symbol.iterator]: function () {
    let index = 0;
    // 返回生成器
    return {
      next() {
        if (index < obj1.actions.length) {
          return {
            done: false,
            value: obj1.actions[index++],
          };
        } else {
          return { done: true };
        }
        return;
      },
    };
  },
};
// 必须写这一行,这样就不会每次创建新的迭代器,创建新的迭代器index永远是0
const obj1Iterator = obj1[Symbol.iterator]();
console.log(obj1Iterator.next()); // {done: false, value: 'run'}
console.log(obj1Iterator.next()); // {done: false, value: 'move'}
console.log(obj1Iterator.next()); // {done: false, value: 'jump'}
console.log(obj1Iterator.next()); // {done: true}
for (const item of obj1) {
  // 调用obj1里面实现的[Symbol.iterator]方法
  console.log(item); // 打印三次,分别是run  move  jump
}

// 可迭代对象优化,使用this和箭头函数
const obj2 = {
  name: "obj2",
  age: 18,
  actions: ["run", "move", "jump"],
  [Symbol.iterator]: function () {
    // 执行时obj2[Symbol.iterator](),this时obj2
    let index = 0;
    // 返回生成器
    return {
      next: () => {
        if (index < this.actions.length) {
          return {
            done: false,
            value: this.actions[index++], // 执行时obj2Iterator.next(),使用了箭头函数这里的this取上层作用域this
          };
        } else {
          return { done: true };
        }
      },
    };
  },
};
const obj2Iterator = obj2[Symbol.iterator]();
console.log(obj2Iterator.next()); // {done: false, value: 'run'}
console.log(obj2Iterator.next()); // {done: false, value: 'move'}
console.log(obj2Iterator.next()); // {done: false, value: 'jump'}
console.log(obj2Iterator.next()); // {done: true}
for (const item of obj2) {
  console.log(item); // 打印三次,分别是run  move  jump
}

// 迭代对象属性
const obj3 = {
  name: "obj3",
  age: 18,
  actions: ["run", "move", "jump"],
  [Symbol.iterator]: function () {
    let index = 0;
    // const arr = Object.keys(this); // 迭代obj3的key, 打印三次,分别是name  age  actions
    // const arr = Object.values(this); // 迭代obj3的value,打印三次,分别是"obj3"  18  ["run", "move", "jump"]
    const arr = Object.entries(this); // 迭代obj3的item
    return {
      next: () => {
        if (index < arr.length) {
          return {
            done: false,
            value: arr[index++],
          };
        } else {
          return { done: true };
        }
      },
    };
  },
};
for (const item of obj3) {
  // 调用obj3里面实现的[Symbol.iterator]方法
  console.log(item); // 打印三次,分别是['name', 'obj3']  ['age', 18]  ['actions', Array(3)]
}

可迭代对象应用

那么可迭代对象可以被用在哪里呢?

  • JavaScript中语法for ...of、展开语法、yield*、解构赋值
  • 创建一些对象时new Map([Iterable])new WeakMap([iterable])new Set([iterable])new WeakSet([iterable])
  • 一些方法的调用Promise.all(iterable)Promise.race(iterable)Array.from(iterable)

原生迭代器对象

事实上平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象:

  • StringArrayMapSetarguments对象、NodeList集合
const actions = ["move", "jump"];
console.log(actions[Symbol.iterator]); // 是个函数
const iterable = actions[Symbol.iterator]();
console.log(iterable.next()); // {value: 'move', done: false}
console.log(iterable.next()); // {value: 'jump', done: false}
console.log(iterable.next()); // {value: undefined, done: true}

自定义类的迭代

我们可以通过class定义一个自己的类,这个类可以创建很多的对象,如果也希望自己的类创建出来的对象是可迭代的,需要在定义类的时候就可以添加上 @@iterator 方法

// 自定义类的迭代
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  [Symbol.iterator]() {
    let index = 0;
    const arr = Object.keys(this);
    return {
      next() {
        if (index < arr.length) {
          return {
            done: false,
            value: arr[index++],
          };
        } else {
          return {
            done: true,
          };
        }
      },
    };
  }
}
const p1 = new Person("p1", 18);
for (const item of p1) {
  console.log(item); // 执行两遍 打印:name  age
}

迭代器的中断

迭代器在某些情况下会在未完全迭代完的情况下被中断

  • 比如遍历的过程中通过 break、return、throw 中断了循环操作

    const obj1 = {
      name: "obj1",
      age: 18,
      [Symbol.iterator]: function () {
        let index = 0;
        const arr = Object.entries(this);
        return {
          next: () => {
            if (index < arr.length) {
              return {
                done: false,
                value: arr[index++],
              };
            } else {
              return { done: true };
            }
          },
        };
      },
    };
    for (const item of obj) {
      console.log(item); // 打印一次:['name', 'obj']
      break; // 循环中断
    }
    
  • 比如在解构的时候,没有解构所有的值

    • 在解构时,迭代的终止并不是由解构操作本身引发的
    • 解构赋值只是自动调用对象的迭代器取出一个新的值,直到解构出所需的元素为止,它并不会主动终止迭代器
    • 终止迭代是因为当解构赋值达到所需的值时,解构过程完成,不再继续请求后续的迭代值
    const obj = {
      [Symbol.iterator]: function () {
        let index = 0;
        const values = [10, 20, 30, 40];
        return {
          next() {
            if (index < values.length) {
              return { done: false, value: values[index++] };
            } else {
              return { done: true };
            }
          },
        };
      },
    };
    
    // 只解构出前两个值,next会执行两次
    const [x, y] = obj;
    console.log(x); // 10
    console.log(y); // 20
    
    

生成器(Generator

定义

生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等

要求

平时会编写很多函数,这些函数终止的条件通常是return或者发生了异常
生成器函数也是一个函数,但是和普通的函数有一些区别:

  • 首先,生成器函数需要在function的后面加一个符号*

  • 其次,生成器函数可以通过yield关键字来控制函数的执行流程

  • 最后,生成器函数的返回值是一个Generator(生成器)

生成器事实上是一种特殊的迭代器

function* generatorFn() {
  console.log(1111);
  console.log(2222);
  yield;
  console.log(3333);
  console.log(4444);
  yield;
  console.log(5555);
  console.log(6666);
}

执行

  • 调用生成器函数不会立即执行它的代码,而是返回一个迭代器对象
  • 通过调用迭代器的 next() 方法来逐步执行生成器函数的代码,yield 语句会暂停执行并返回值
  • 所有的 yield 语句都被执行后,生成器函数执行完毕,返回的 done 属性为 true
  • 不希望next返回的是一个undefined,这时可以通过yield来返回结果
function* generatorFn() {
  console.log(1111); // 执行第一个next()是打印
  console.log(2222); // 执行第一个next()是打印
  yield 1122;
  console.log(3333); // 执行第二个next()是打印
  console.log(4444); // 执行第二个next()是打印
  yield;
  console.log(5555); // 执行第三个next()是打印
  console.log(6666); // 执行第三个next()是打印
}

const generator = generatorFn();
console.log(generator); // generatorFn
console.log(generator.next()); // {value: 1122, done: false}
console.log(generator.next()); // {value: undefined, done: false}
console.log(generator.next()); // value: undefined, done: true}
console.log(generator.next()); // {value: undefined, done: true}

传参

调用next函数的时候可以给它传递参数,这个参数会作为上一个yield语句的返回值

  • 当第一次调用 next() 时,由于生成器还没有开始执行传入的参数会被忽略,因此在生成器函数做参数接收
  • 从第二次 next() 开始,传入的参数会作为 yield 表达式的返回值使用
function* generatorFn(num1) {
  console.log(num1); // 第一个参数通常不在next中传,而是函数接收
  console.log(1111); // 执行第一个next()是打印
  console.log(2222); // 执行第一个next()是打印
  const name = yield 1122;
  console.log(name); // next2
  console.log(3333); // 执行第二个next()是打印
  console.log(4444); // 执行第二个next()是打印
  yield;
  console.log(5555); // 执行第三个next()是打印
  console.log(6666); // 执行第三个next()是打印
}

const generator = generatorFn(12);
console.log(generator); // generatorFn
console.log(generator.next()); // {value: 1122, done: false}
console.log(generator.next("next2")); // {value: undefined, done: false}
console.log(generator.next()); // value: undefined, done: true}
console.log(generator.next()); // {value: undefined, done: true}

提前结束

生成器可以在执行过程中通过 return 语句提前结束,使用 return 可以停止生成器的迭代,并且可以将一个值作为生成器的结束值返回,这个返回值可以通过 throw 来捕获

function* generatorFn() {
  console.log(1111); // 执行第一个next()是打印
  console.log(2222); // 执行第一个next()是打印
  yield 1122;
  console.log(3333); // 执行第二个next()是打印
  console.log(4444); // 执行第二个next()是打印
  yield;
  console.log(5555); // 执行第三个next()是打印
  console.log(6666); // 执行第三个next()是打印
}

const generator = generatorFn(12);
console.log(generator); // generatorFn
console.log(generator.next()); // {value: 1122, done: false}
console.log(generator.return("next2")); // {value: undefined, done: true}
console.log(generator.next()); // value: undefined, done: true}
console.log(generator.next()); // {value: undefined, done: true}

抛出异常

JavaScript 中,生成器函数可以使用 throw 语句抛出异常,并在生成器内部捕获这些异常。抛出异常可以使你在生成器执行过程中处理错误情况,类似于传统的 try...catch 机制

function* myGenerator() {
  try {
    yield "Step 1";
    yield "Step 2";
  } catch (error) {
    console.log(error.message); // Custom error
    yield "Step 3 after error"; // 在 catch 块中继续 yield
  } finally {
    console.log("Finally block executed.");
  }
}

const gen = myGenerator();

console.log(gen.next()); // { value: 'Step 1', done: false }
console.log(gen.next()); // { value: 'Step 2', done: false }
console.log(gen.throw(new Error("Custom error"))); // {value: 'Step 3 after error', done: false}
// Finally block executed.
console.log(gen.next()); // { value: 'Step 3 after error', done: false }
console.log(gen.next()); // { value: undefined, done: true }

替换迭代器

使用 yield* 来处理一个可迭代对象,相当于是yield的语法糖,会依次迭代这个可迭代对象,每次迭代其中的一个值

// 生成器替代迭代器
const names = ["abc", "def", "ghi"];
function* createArrIterator(arr) {
  // for (let i = 0; i < arr.length; i++) {
  //   yield arr[i];
  // }

  // 使用语法糖
  yield* arr;
}
const fnNamesIterator = createArrIterator(names);
console.log(fnNamesIterator.next()); // {done: false, value: 'abc'}
console.log(fnNamesIterator.next()); // {done: false, value: 'def'}
console.log(fnNamesIterator.next()); // {done: false, value: 'ghi'}
console.log(fnNamesIterator.next()); // {value: undefined, done: true}

生成器实现自定义类迭代

使用 *[Symbol.iterator] 在类中写迭代器

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  *[Symbol.iterator]() {
    yield* Object.keys(this);
  }
}
const p1 = new Person("p1", 18);
for (const item of p1) {
  console.log(item); // 执行两遍 打印:name  age
}
console.log(p1[Symbol.iterator]); // 生成器函数

生成器和其它异步处理方案

/* 模拟请求 */
// 方式一
function requestData1(url, callback) {
  setTimeout(() => {
    console.log(url);
    callback && callback(url);
  }, 2000);
}
// 方式二
function requestData2(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(url);
      resolve(url);
    }, 2000);
  });
}

/* 模拟调用场景
  第一次调用拿到数据
  第二次的请求url依赖于第一次的结果
  第三次的请求url依赖于第二次的结果
  依次类推
*/
// 方式一:普通方法
function getData1() {
  requestData1("https://hello", (res1) => {
    requestData1(`${res1}/china`, (res2) => {
      requestData1(`${res2}/global`, (res3) => {
        requestData1(`${res3}/world`, (res4) => {
          // 依次递推就是回调地狱
        });
      });
    });
  });
}
// 方式二:Promise链式调用
function getData2() {
  requestData2("https://hello")
    .then((res1) => {
      requestData2(`${res1}/china`).then((res2) => {
        requestData2(`${res2}/global`).then((res3) => {
          requestData2(`${res3}/world`).then((res4) => {
            // 依次递推依旧回调地狱
          });
        });
      });
    })
    .catch((err) => {});
}
// 方式三:Promise优化
function getData3() {
  requestData2("https://hello")
    .then((res1) => {
      return requestData2(`${res1}/china`); // return Promise,状态由这个Promise决定,可以再调用then
    })
    .then((res2) => {
      return requestData2(`${res2}/global`);
    })
    .then((res3) => {
      return requestData2(`${res3}/world`);
    })
    .then((res4) => {
      // 依次类推解决了回调地狱
    })
    .catch((err) => {});
}

// 方式四:生成器Promise方案
function* getData4() {
  const res1 = yield requestData2("https://hello");
  const res2 = yield requestData2(`${res1}/china`); // res1是next传来的参数也就是第一次Promise请求完得到的值
  const res3 = yield requestData2(`${res2}/global`);
  const res4 = yield requestData2(`${res3}/world`);
}
// const gen = getData4();
// console.log(gen.next()); // {value: Promise, done: false}
// gen.next().value.then((res1) => {
//   gen.next(res1).value.then((res2) => {
//     gen.next(res2).value.then((res3) => {
//       gen.next(res3).value.then((res4) => {
//         // 依次类推也有回调地狱问题,下面优化
//       });
//     });
//   });
// });

// autoExecute(getData4);

// 方式五:生成器优化,自动执行生成器
function autoExecute(genFn) {
  const gen = genFn();
  function exec(res) {
    const result = gen.next(res);
    if (result.done) return result.value;
    result.value.then((res) => {
      exec(res);
    });
  }
  exec();
}

// 方式六:使用async/await和Promise
async function getData6() {
  const res1 = await requestData2("https://hello");
  const res2 = await requestData2(`${res1}/china`);
  const res3 = await requestData2(`${res2}/global`);
  const res4 = await requestData2(`${res3}/world`);
}

getData1();
getData2();
getData3();
getData6();
  • 8
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中的迭代器生成器和装饰器都是非常重要的编程概念,下面分别介绍一下: 1. 迭代器 Python中的迭代器是一种可以遍历集合中元素的对象,可以使用for循环遍历集合中的元素。迭代器实现了两个方法:__iter__()和__next__()。__iter__()方法返回迭代器对象自身,__next__()方法返回集合中的下一个元素。 下面是一个简单的使用迭代器遍历列表的示例: ``` my_list = [1, 2, 3] my_iterator = iter(my_list) for i in my_iterator: print(i) ``` 2. 生成器 生成器是一种特殊的函数,可以在执行过程中多次返回值,而不是只返回一次。生成器使用yield语句返回值,可以暂停函数的执行,并在需要时继续执行。 下面是一个简单的生成器示例: ``` def my_generator(): yield 1 yield 2 yield 3 for i in my_generator(): print(i) ``` 3. 装饰器 装饰器是一种可以修改函数或类的行为的函数,可以在不修改原始代码的情况下添加额外的功能。装饰器本质上是一个可以接受函数或类作为参数的函数,可以在不修改原始函数或类的情况下修改其行为。 下面是一个简单的装饰器示例: ``` def my_decorator(func): def wrapper(): print("Before the function is called.") func() print("After the function is called.") return wrapper @my_decorator def my_function(): print("Inside the function.") my_function() ``` 在上面的代码中,我们定义了一个装饰器函数my_decorator,它接受一个函数作为参数,并返回一个新的函数wrapper。这个新函数在调用原始函数之前和之后打印一些文本。我们使用@符号将装饰器应用到my_function函数上,这样my_function函数的行为就被修改了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值