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迭代对象:
如您所见,我们添加了返回包含我们实现协议的函数的[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
};
}
};
}
//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)];
}
}
生成器函数:
生成器函数只是一种语法糖。在我们自己实现的iterator函数中,我们需要跟踪内部状态,例如 ` valuea` 和 `b` done。生成器函数返回一个特殊的迭代器,称为 `i`。Generator
生成器函数使用特定的function*语法声明。它使用一个特殊的关键字yield,以便在迭代过程中提供值。
//definde function with *
function* counter() {
//yield something here
}
收益率与回报
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}
如您所见,当我们创建实例时,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}
如你所见,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()}]`;
}
}
需要检查的主要代码部分是:
*[Symbol.iterator]() {
let currentNode = this.head;
while (currentNode) {
yield currentNode.value;
currentNode = currentNode.next;
}
}
你可以看到我是如何通过在LinkedList前面加上[Symbol.iterator].来使其成为可迭代对象的,并且我正在遍历值直到整个列表耗尽。*[Symbol.iterator]generatoryield
接下来要看的部分是toString
toString() {
return `[${[...this].toString()}]`;
}
这里可以看到我利用了可迭代对象数组中的展开运算符。我先将其展开成一个数组,然后利用toString数组对象的特性。
- Redux Saga
最近我了解到,Redux 库之一Saga大量使用了生成器。
这里用到的一些示例可以在这个 Codesandbox 中找到。
感谢阅读。
请阅读我的其他文章
JavaScript Jungle:JS 中稀疏数组的奇特案例
Vikas yadav 为 XenoX 撰写 ・ 2021年9月19日
关注我的推特




