谈一下什么是可迭代协议前言 近期看到一个问题就是,如何来遍历一个对象,以及对象是否可以进行遍历。谈到这个我第一想法是当然
前言
近期看到一个问题就是,如何来遍历一个对象,以及对象是否可以进行遍历。谈到这个我第一想法是当然可以遍历,比如使用 for in
或者Object.keys``Object.values
都可以对对象进行遍历取值呀,但是却是不对的,要实现像数组一样,是可以直接通过for
或者是for of
这种方式来进行遍历的,我通过查询发现了可遍历的关键因素
可迭代协议
我们可以先看一些对于 for of
的解释。for of
语句执行一个循环,该循环处理来自可迭代对象的值序列。那么什么是可迭代对象呢,可迭代对象是指那些实现了迭代协议的对象。可迭代协议要求对象具有一个 Symbol.iterator
属性,该属性是一个函数,当调用时返回一个对象,该对象遵循迭代器协议。这样的对象可以在 for...of 循环中被遍历,切会返回迭代器获得的迭代值,我们可以打印一下数组,看一下是否存在Symbol.iterator
现在我们就知道了如何让对象进行遍历。大致可以分为,在属性上增加一个Symbol.iterator
,且在调用时返回一个遵循迭代协议的对象。
迭代器协议
迭代器协议是一个定义对象如何按需产生值的规范,只有实现了特定的语法,一个对象才可以成为迭代器,迭代器对象必须实现一个 next()
方法。这个方法不接受参数(或接受可选的参数),并返回一个对象,返回的对象内必须存在两个属性,done
表示是否迭代完成,如果迭代完成则返回true
反之则返回false
,value
表示当前迭代的值,当done
为true
时也就是迭代完成的时候,value
可以省略
const myIterator = {
current: 0,
last: 5,
next() {
if (this.current < this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
上面便是一个符合迭代器协议的对象,他有一个next()
方法,并且会返回当前的值以及是否遍历完成。
对象实现迭代
我们上面已经了解了迭代器协议和可迭代协议,那么我们就可以实现对象的遍历了,只需要在对象上添加一个Symbol.iterator
属性并且返回上方的可迭代协议就可以实现
const myIterable = {
[Symbol.iterator]() {
let current = 0;
const last = 5;
return {
next() {
if (current < last) {
return { done: false, value: current++ };
} else {
return { done: true };
}
}
};
}
};
for (const value of myIterable) {
console.log(value); // 0, 1, 2, 3, 4
}
这样我们便实现了对象的遍历
对象迭代的优化
上面可以实现对象使用迭代,但是我们的对象都是有值的,那么如何遍历的时候返回的对象是我们想要拿到的对象内的值呢,而且我们可以直接使用生成器函数搭配yield
来使用
const myObject = {
a: '1aa',
b: '2cc',
c: '3dd',
d: '4ee',
*[Symbol.iterator]() {
for (const key in this) {
if (this.hasOwnProperty(key)) {
yield this[key];
}
}
}
};
我们改为使用生成器函数内部使用yield
来实现,yield
是生成器函数的一部分,用于在函数执行过程中生成一个序列的值,并且可以在生成值后暂停函数的执行,但不会终止函数的执行,并且可以使用next()
方法,每次调用next()
方法函数就会继续执行,直到遇到下一个yield
暂停。
迭代器的使用场景
上面我们虽然对对象实现了可迭代,但是这种情况一般不会出现在开发中,并且在开发中完全可以使用Object.keys
或者Object.values
来实现,对于对象实现可迭代一般都是出现在面试场景中,但是日常开发中确实有时候也会遇到使用可迭代协议的地方例如:
自定义数据结构的遍历
比如你实现了一个复杂的数据结构(如树、图、链表等),希望能够通过 for...of 循环来遍历这个数据结构的元素。在这种情况下,实现可迭代协议就非常有用。例如,一个二叉树的数据结构:
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
*inOrderTraversal() {
if (this.left) yield* this.left.inOrderTraversal();
yield this.value;
if (this.right) yield* this.right.inOrderTraversal();
}
[Symbol.iterator]() {
return this.inOrderTraversal();
}
}
// 使用示例
const root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
for (const value of root) {
console.log(value); // 输出:4, 2, 5, 1, 3
}
创建延迟计算的序列
比如你可能希望创建一个不立即计算出所有元素的序列,而是按需生成元素。例如,生成一个无限的斐波那契数列
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
// 使用示例
const sequence = fibonacci();
console.log(sequence.next().value); // 输出:1
console.log(sequence.next().value); // 输出:1
console.log(sequence.next().value); // 输出:2
console.log(sequence.next().value); // 输出:3
console.log(sequence.next().value); // 输出:5
这里,生成器函数 fibonacci
实现了迭代器协议,并且通过 for...of
或 next()
方法可以按需获取斐波那契数列的下一个值,而不需要一次性计算出所有值
结尾
这里呢我们通过如何来遍历对象了解了可迭代协议以及迭代器协议,其实把对象变为可迭代实际上是在日常开发中基本上遇不到的,而且我们有更好的api
来实现迭代对象拿到值,这里的目的主要是了解迭代器协议,来应对面试或是日常开发中可以更简便实现一些效果的方式。
转载自:https://juejin.cn/post/7410616997006688296