迭代器与生成器

迭代器

可迭代对象

可迭代对象(Iterable)是一种特殊的 JavaScript 对象,它实现了 迭代器协议。这意味着该对象定义了它自己的迭代行为,可以在 for...of 循环或其他期望可迭代对象的上下文中使用。

迭代器协议,具体来说:

  1. 实现 [Symbol.iterator]() 方法: 可迭代对象必须实现一个名为 [Symbol.iterator]() 的方法。这个方法返回一个迭代器对象,该迭代器对象定义了迭代过程。
  2. 迭代器对象: 迭代器对象必须实现 next() 方法。每次调用 next() 方法时,它都会返回一个对象,该对象有两个属性: value 和 donevalue 属性包含当前迭代的值,done 属性是一个布尔值,表示迭代是否结束。
  3. 可在 for...of 中使用: 由于可迭代对象实现了迭代器协议,因此它们可以在 for...of 循环中使用,以及其他期望可迭代对象的上下文中使用。

自定义可迭代对象

const testObj = {}

testObj[Symbol.iterator] = function () {
  let i = 0
  return {
    next: function () {
      return {
        value: i++,
        done: i > 10
      }
    }
  }
}
/**
 * 第一次 
 * {value:0,done:false}
 * 第二次
 *  {value:1,done:false}
 * 
 * ……
 * 
 * 第十次
 * {value:9,done:false}
 * 第十一次
 * {value:10,done:true}
 */


for (let i of testObj) {
  console.log(i)
}

可迭代对象的被迭代过程

  1. 调用可迭代对象的[Symbol.iterator]()方法:该方法被称为迭代器工厂函数,用以生成迭代器对象
  2. 调用Symbol.iterator方法返回的迭代器对象:该对象就是迭代器对象,负责迭代过程。
  3. 使用 next 方法进行迭代: 迭代器对象上有next方法,调用next方法,返回一个迭结果对象 IteratorResult {value: any, done: boolean}。
  4. 迭代结果对象:{value: any, done: boolean},如果整个迭代过程完成,也就是所有数据都迭代完了那么 done会变成true,否则就是false。value就是当前迭代的值。如果全部数据都迭代完,value就会变成undefined
  5. 迭代器对象的生命周期: 迭代器对象是一次性的,只能做一个整体的迭代,迭代完成之后就会被销毁,不能再次使用。

实验

const arr = [1, 2, 3, 4];

const iter = arr[Symbol.iterator]();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

迭代器的提前终止与关闭

迭代器的 return 方法

  • return 方法: 某些迭代器对象实现了一个可选的 return 方法。调用这个方法可以提前终止迭代过程,并关闭迭代器对象。调用 return 方法后,迭代器就不能再使用了。
  • 用途return 方法通常用于清理资源或在迭代提前终止时执行特定操作。
  • 注意: 并不是所有的迭代器都有 return 方法。例如,数组的默认迭代器就没有 return 方法,因此不能通过这种方式提前关闭。

提前终止迭代的情况

  1. 解构操作未消费所有值(了解即可):
    • 当使用解构赋值时,如果没有消费迭代器的所有值,迭代就会提前终止。
    • 例如,解构一个数组到有限个变量时,剩余的元素不会被迭代。
  2. for...of 循环的提前退出:
    • for...of 循环可以通过以下方式提前退出:
      • break: 立即退出循环。
      • continue: 跳过当前迭代,继续下一个迭代。
      • return: 从包含循环的函数中返回,终止循环。
      • throw: 抛出异常,终止循环。

在这些情况下,如果迭代器对象实现了 return 方法,JavaScript 引擎会自动调用 return 方法来关闭迭代器。

实验

const myIterable = {
  [Symbol.iterator]() {
    let step = 0;
    return {
      next() {
        step++;
        if (step <= 3) {
          return { value: step, done: false };
        }
        return { done: true };
      },
      return() {
        console.log('Iterator closed');
        return { done: true };
      }
    };
  }
};

const iterator = myIterable[Symbol.iterator]();

for (const value of iterator) {
  console.log(value);
  if (value === 2) {
    break; // 提前退出循环
  }
}

// 输出:
// 1
// 2
// Iterator closed

在这个示例中,myIterable 的迭代器实现了 return 方法。当 for...of 循环因 break 提前退出时,return 方法被调用,输出 "Iterator closed"。

迭代器本身也是可迭代的

在 JavaScript 中,迭代器对象本身通常也是可迭代的。这意味着迭代器对象实现了 [Symbol.iterator]() 方法,并返回自身。当你迭代一个迭代器对象时,实际上是迭代产生该迭代器可迭代对象。这与直接迭代可迭代对象的效果相同。

有点抽象,注意区分不同颜色的概念,看个实验

const iterable = [1, 2, 3];
const iterator = iterable[Symbol.iterator]();

console.log(iterator[Symbol.iterator]() === iterator); // true

for (const value of iterator) {
  console.log(value); // 输出: 1, 2, 3
  // 迭代iterator这个迭代器对象与迭代iterable这个可迭代对象,效果是一样的
}

生成器

生成器函数的定义方式:

function * generatorFn()
let generatorFn = function * () {}
let foo = {    * generatorFn() {}}
class Foo {    * generatorFn (){}}
class Bar {    static * generatorFn () {}}

注意:箭头函数是不能用来定义生成器函数的

yield关键字(核心三字诀)

在例子中体会其含义

生成器函数执行

不会让内部代码执行,只会产生一个生成器对象并返回。

生成器对象

生成器对象控制生成器函数的执行,并提供了一种暂停和恢复代码执行的机制。

生成器对象的特性

  1. 执行控制器:
    • 生成器对象控制生成器函数的执行。调用 next() 方法会使生成器函数的代码从上次暂停的位置继续执行(对应yield的),直到遇到下一个 yield 表达式或函数结束(对应yield的)。
  2. 状态:
    • suspended(暂停): 当生成器函数执行到 yield 表达式并暂停时,生成器对象处于 suspended 状态。
    • closed(关闭): 当生成器函数执行完成或被关闭时,生成器对象处于 closed 状态。
  3. next() 方法:
    • 调用 next() 方法会返回一个对象 { value, done }
    • 如果生成器函数有一个最终的 return 语句,当 done 为 true 时,value 是 return 返回的值。
    • 如果生成器函数执行完毕,后续调用 next() 会返回 { done: true, value: undefined }
  4. 自引用:
    • 生成器对象是可迭代的,其 [Symbol.iterator]() 方法返回生成器对象自身。这意味着生成器对象可以在 for...of 循环中直接使用。
  5. return() 方法:
    • 生成器对象实现了 return() 方法,用于提前关闭生成器。调用 return() 会使生成器函数立即终止,并将 done 设置为 true
  6. throw() 方法:
    • throw() 方法用于在生成器内部抛出错误。这会导致生成器函数在当前位置抛出指定的异常。

仍旧是非常抽象零散的概念,做一个实验看看。

实验 

 

注意:这里“给”的值是return回来的,而不是yield出来的。 

生成器与迭代器的结合

一般我们不会写这么抽象的生成器函数。

生成器对象本身就是一种可迭代对象。我们可以直接:

function* generatorFn() {
    yield 1;
    yield 2;
    yield 3;
}

const g = generatorFn();

for (let i of g) {
    console.log(i);
}

因为生成器对象首先实现了 [Symbol.iterator]() 方法,其迭代器工厂函数执行返回的其实就是他自己这个生成器对象本身。而且生成其本身就有next()函数,next()返回的也恰好是{ value, done }类型,符合迭代对象规范

补充:Symbol新的原始值数据类型

在 JavaScript 中,Symbol 是一种新的原始数据类型,用于创建独一无二的标识符。与其他原始数据类型(如 NumberStringBooleanUndefined 和 Null)不同,Symbol 是为避免对象属性名冲突而设计的。

特性和用法

  1. 唯一性:
    • 每个 Symbol 值都是唯一的,即使它们的描述相同。
    • Symbol 不能与其他类型进行隐式转换。
  2. 创建 Symbol:
    • 使用 Symbol() 函数创建,不使用 new 关键字。
    • 可以提供一个可选的描述字符串,用于调试和日志记录。
  3. 作为对象属性:
    • Symbol 可以用作对象的属性名,避免属性名冲突。
    • 使用方括号语法来定义和访问 Symbol 属性。
let s1 = Symbol('foo');
let o = {
  [s1]: 'foo val'
};

意义

  1. 避免属性名冲突:
    • Symbol 属性不会与字符串属性发生冲突,是定义对象属性的安全方式。
  2. 用于创建内置方法和属性:
    • JavaScript 使用 Symbol 来定义一些内置方法和属性,增强语言的灵活性。

获取 Symbol 属性

  • Object.getOwnPropertySymbols():
    • 获取对象的所有 Symbol 属性。

全局 Symbol 注册表

  • Symbol.for():
    • 在全局注册表中查找或创建一个 Symbol。如果已存在,则返回该 Symbol,否则创建一个新的。
  • Symbol.keyFor():
    • 返回一个全局 Symbol 的键(描述),如果不存在则返回 undefined
let s = Symbol.for('foo');
console.log(Symbol.keyFor(s)); // 输出: 'foo'

内部方法和属性

JavaScript 定义了一些内置的 Symbol 值,用于改变语言的行为:

  • Symbol.asyncIterator: 定义对象的默认异步迭代器。
  • Symbol.hasInstance: 用于自定义 instanceof 操作符的行为。
  • Symbol.isConcatSpreadable: 指示对象是否可以在数组的 concat() 方法中展开。
  • Symbol.iterator: 定义对象的默认迭代器。
  • 正则表达式相关:
    • Symbol.match: 定义正则表达式的匹配行为。
    • Symbol.replace: 定义正则表达式的替换行为。
    • Symbol.search: 定义正则表达式的搜索行为。
    • Symbol.split: 定义正则表达式的分割行为。
  • Symbol.species: 控制派生对象的构造函数。
  • Symbol.toPrimitive: 定义对象的原始值转换。
  • Symbol.toStringTag: 改变对象的默认字符串描述。
  • Symbol.unscopables: 指定对象的哪些属性不会被 with 环境绑定。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值