发布于 2026-01-06 9 阅读
0

JavaScript Jungle:将任何对象转换为 Iterable

JavaScript Jungle:将任何对象转换为 Iterable

ES6发布时引入了两个重要的协议:`Object`Iterable和`Object.getObject() Iterator`。基本上,我们可以通过实现这两个协议将任何类型的object对象转换为 `Object` 。通过将对象转换为 `Object`,我们可以使用 `Object.getObject()` 循环。我们还可以在数组(在 JavaScript 中是`Object` 符号)上对这些对象使用 `Object.getObject()`。首先,让我们了解一下这两个协议:iterableprotocolsobjectiterablefor...ofspread operator...

可迭代协议:

根据MDN

iterable protocol允许 JavaScript 对象define或其customize属性iteration behavior,例如在for...of构造中循环遍历哪些值。

简单来说,这是一条规则,遵循这条规则我们可以做到两件事:

  • 如果一个对象已经是可迭代的,我们可以修改它现有的属性。iteration behaviour
  • 如果一个对象不可迭代,我们可以iteration为其添加行为。

如果你是 JavaScript 初学者,那么你已经使用过一些著名的可迭代Array对象了。当然,JavaScript 中还有其他内置的可迭代对象iterables。例如:

  • 地图
  • 弱图
  • 弱集

现在,最主要的问题出现了。

我们该如何实现这个协议?

这很简单。我们只需要实现它@@iterator。这@@iterator是 JavaScript 中的一个特殊属性。因此,要创建任何对象iterable,我们都需要添加这个@@iterable属性。

我们可以使用常量符号来实现这一点[Symbol.iterator]。如果您不知道什么是符号,请点击此处阅读。

@@iterator应该是一个简单的方法,no arguments它将返回一个符合要求的值iterator protocol

总而言之,我们可以Iterable通过以下步骤将任何对象转换为:

  • 取一个物体
  • 通过添加@@iterable属性[Symbol.iterator]
  • @@iterable应该是一种no argument方法
  • 方法的返回值@@iterable应该是一个iterator

图像

在深入探索 Iterable Jungle 之前,我们先来谈谈……iterator protocol

迭代器协议:

根据MDN

iterator protocol定义了一种生成一系列值(有限或无限)的标准方法,并且可能在生成所有值后返回一个值。

简单来说:

  • 这是一条定义shape迭代过程中值的规则。
  • 它还需要告诉我们,no more values当我们遍历完所有值之后,还有哪些值存在。

要创建任何对象,iterator我们需要实现next()一个方法,该方法将返回一个具有以下两个属性的对象:

  • - 在迭代期间可用的值
  • 完成- 布尔值,表示是否还有更多值

这很简单,不是吗?这里有一个Infinite Counter迭代器的例子。

图像

finite counter你也可以创建一个迭代器。

图像

注意,当达到限制时,我们会返回done: true。这是为了告诉迭代器式for...of循环,没有更多值了,可以停止循环。

既然我们已经知道如何实现iterator,那就回到我们的项目中iterable,把它完全实现出来吧。

所以,在我们的例子中,我们希望user在循环迭代时iterable返回结果。如果您尝试在未实现该函数的情况下进行迭代,将会收到以下错误:[key, value]for...ofuserfor...ofiterable

TypeError:用户不可迭代

图像

以下是使用codesandbox实现的可user迭代对象:

截图时间:2021年10月2日 下午6:38:29

如您所见,我们添加了返回包含我们实现协议的函数的[Symbol.iterator]对象的内部函数next()iterator

如果我们使用一种叫做生成器函数的特殊函数,就可以减少一些代码。

 // with our own implementation of iterator 
 [Symbol.iterator]: function () {
    const keys = Object.keys(this);
    let index = 0;

    return {
      next: () => {
        if (index < keys.length) {
          const key = keys[index];
          const val = this[key];
          index++;
          return {
            value: [key, val],
            done: false
          };
        }
        return {
          value: undefined,
          done: true
        };
      }
    };
  }
Enter fullscreen mode Exit fullscreen mode

//with Generator function
[Symbol.iterator]: function* () {
    const keys = Object.keys(this);
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i];
      const val = this[key];
      yield [(key, val)];
    }
  }
Enter fullscreen mode Exit fullscreen mode

生成器函数:

生成器函数只是一种语法糖。在我们自己实现的iterator函数中,我们需要跟踪内部状态,例如 ` valuea` 和 `b` done。生成器函数返回一个特殊的迭代器,称为 `i`。Generator

生成器函数使用特定的function*语法声明。它使用一个特殊的关键字yield,以便在迭代过程中提供值。


//definde function with * 

function* counter() {
 //yield something here
}
Enter fullscreen mode Exit fullscreen mode

收益率与回报

yield与 `return` 非常不同return。当我们从函数返回时,仅仅意味着执行结束,我们退出了函数。而当我们调用yield生成器函数时,它会暂停执行,并跟踪接下来要生成什么。因此,当我们next再次调用生成器函数时,它会yield生成下一个值。

我们来看一个例子。

// defined Counter generator
function* Counter() {
  yield 1;
  yield 2;

}
// create an instance of COunter
const counterInstance = Counter();

//first call 
console.log(counterInstance.next()) 
//{done: false, value: 1}

// second call 
console.log(counterInstance.next()) 
//{done: false, value: 2}

// Third call 
console.log(counterInstance.next()) 
//{done: true, value: undefined}
Enter fullscreen mode Exit fullscreen mode

如您所见,当我们创建实例时,generator它会返回一个对象iterator。它执行以下操作:

  • 第一次拨打电话时,next会有yield一个{done: false, value: 1}短暂的停顿。
  • 当我们next再次调用时,它会跟踪其状态并yield {done: false, value: 2}
  • 当我们最后呼唤,next因为没有什么可以再让步了,它就给了我们{done: true, value: undefined}

next()完成后你可以继续打电话,但总是会给你{done: true, value: undefined}

现在让我们使用生成器来生成我们的数据。Infinite Counter

带生成器的无限计数器

function* InfiniteCounter() {
  let count = 0;
  while(count !== Number.infinity) {
    yield ++count;
  }

}

const counterInstance = InfiniteCounter();

console.log(counterInstance.next()) 
// {done: false, value: 1}
console.log(counterInstance.next()) 
// {done: false, value: 2}
console.log(counterInstance.next()) 
// {done: false, value: 3}
Enter fullscreen mode Exit fullscreen mode

如你所见,Generator它干净多了。

你可能会想,这当然很棒。但我不想仅仅为了创建一个物体就做这么多工作Iterable。我已经有了,Object.entries我会用到它。请给我一些实际的例子。

就是这样。

实际示例

  • 链表

我将实现一个非常基础的链表。它只包含以下方法。

  • 添加 - 向链表中添加新元素
  • size - 获取链表大小的方法
  • head - 获取 head 节点的方法
  • tail - 用于获取 tail 的 getter
class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

class LinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
    this.count = 0;
  }

  get size() {
    return this.count;
  }

  add(value) {
    const node = new Node(value);

    if (!this.head) {
      this.head = node;
    } else {
      const tail = this.tail;
      tail.next = node;
    }
    this.tail = node;
    this.count++;
  }

  *[Symbol.iterator]() {
    let currentNode = this.head;
    while (currentNode) {
      yield currentNode.value;
      currentNode = currentNode.next;
    }
  }

  toString() {
    return `[${[...this].toString()}]`;
  }
}
Enter fullscreen mode Exit fullscreen mode

需要检查的主要代码部分是:

*[Symbol.iterator]() {
    let currentNode = this.head;
    while (currentNode) {
      yield currentNode.value;
      currentNode = currentNode.next;
    }
  }
Enter fullscreen mode Exit fullscreen mode

你可以看到我是如何通过LinkedList前面加上[Symbol.iterator].来使其成为可迭代对象的,并且我正在遍历值直到整个列表耗尽。*[Symbol.iterator]generatoryield

接下来要看的部分是toString

toString() {
    return `[${[...this].toString()}]`;
  }
Enter fullscreen mode Exit fullscreen mode

这里可以看到我利用了可迭代对象数组中的展开运算符。我先将其展开成一个数组,然后利用toString数组对象的特性。

  • Redux Saga

最近我了解到,Redux 库之一Saga大量使用了生成器。

这里用到的一些示例可以在这个 Codesandbox 中找到。

感谢阅读。

请阅读我的其他文章

关注我的推特

参考

文章来源:https://dev.to/xenoxdev/javascript-jungle-convert-any-object-to-iterable-40l6