es6:关于迭代器,你了解多少?本篇文章会通俗易懂的方式介绍迭代器,帮助友友们理解迭代器,并进一步剖析更深一层的知识点,
一:遍历器 Iterator (迭代器)
-
js中有多种可以被称之为集合的数据结构(
arr, obj, set, map
) -
我们希望某些数据结构是可以被迭代的,于是官方就打造了一个属性
Iterator
,并设定具有Iterator
属性的数据结构就是可迭代的 -
迭代器属性的值必须是一个对象,且对象中必须拥有next方法,该next每次被调用,就会返回一个新对象 {
done: false, value: x
} -
拥有迭代器属性的数据结构才可以被 for - of 遍历
-
for of 遍历的其实是某结构上的迭代器对象
二:手写迭代器
下面带友友们实现一个简单的迭代器函数 createIterator
,用来迭代一个数组 items
,可以返回一个对象,该对象有一个 next
方法,每次调用该方法都会返回下一个元素的信息,直到数组中的所有元素都被遍历完
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = i >= items.length;
var value = !done ? items[i++] : undefined
return {
done: done,
value: value
}
}
}
}
var iterator = createIterator([1, 2, 3])
console.log(iterator.next()); // { done: false, value: 1 }
console.log(iterator.next()); // { done: false, value: 2 }
console.log(iterator.next()); // { done: false, value: 3 }
console.log(iterator.next()); // { done: true, value: undefined }
- 第一次调用
iterator.next()
时,i
为 0,因此value
为1
,并且done
为false
。 - 第二次调用
iterator.next()
时,i
已经递增到 1,因此value
为2
,并且done
仍然为false
。 - 第三次调用
iterator.next()
时,i
已经递增到 2,因此value
为3
,并且done
仍然为false
。 - 第四次调用
iterator.next()
时,i
已经递增到 3,此时i
大于等于数组长度,因此done
为true
,而value
为undefined
。
三:对象迭代 VS 数组迭代
对象迭代
对于普通的 JavaScript 对象,它本身并不具备迭代能力,要使一个普通对象可迭代,需要在其原型链上定义 Symbol.iterator
方法,这个方法应该返回一个迭代器对象,该对象有一个 next
方法用于获取序列中的下一个元素。
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = i >= items.length;
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
// 定义一个普通对象
const obj = {
value: 1
};
// 为 obj 添加一个 Symbol.iterator 属性,使其可迭代
obj[Symbol.iterator] = function() {
return createIterator([1, 2, 3]);
};
// 使用 for...of 循环遍历 obj
for (let value of obj) {
console.log(value);
}
这段代码的核心在于将一个迭代器绑定到了普通对象 obj
的 Symbol.iterator
属性上。这意味着尽管 obj
本身没有 [1, 2, 3]
这些值,但由于 Symbol.iterator
的定义,for...of
循环认为 obj
包含这些值,并按顺序遍历它们。但值得注意的一点是,这里的 obj
对象定义了一个 value
属性,但是在后续的代码中并没有使用到这个属性,这是因为定义了 obj
的 Symbol.iterator
属性后,for...of
循环将忽略 obj
中的其他属性,仅遍历由 Symbol.iterator
方法返回的迭代器所指定的值。
数组迭代
数组是一种原生支持迭代的数据结构。每个数组都有一个内置的 Symbol.iterator
方法,可以用来生成一个迭代器,用于遍历数组的元素。我们可以直接使用 for...of
循环来迭代数组。
const arr = [1, 2, 3];
for (let value of arr) {
console.log(value);
}
比较
-
原生支持:
- 数组:数组是原生支持迭代的数据结构,可以直接使用
for...of
或者其他迭代机制。 - 对象:普通对象本身不支持迭代,需要手动定义
Symbol.iterator
方法才能迭代。
- 数组:数组是原生支持迭代的数据结构,可以直接使用
-
迭代方式:
- 数组:数组的
Symbol.iterator
返回一个迭代器,该迭代器按顺序返回数组中的每个元素。 - 对象:对于普通对象,你需要定义自己的逻辑来决定如何迭代其属性或值。
- 数组:数组的
四:手写for of
上面讲到数组是原生支持迭代的,可以直接使用 for...of
,那么这个方法是怎么找到的呢?现在带友友们解析一下
-
检查对象是否可迭代:
if (obj[Symbol.iterator]) {
: 检查obj
是否具有Symbol.iterator
属性。throw new TypeError(obj + " is not iterable");
: 如果对象有Symbol.iterator
属性,则抛出一个TypeError
,这是因为函数预期接收不可迭代对象作为参数,但接收到的是一个可迭代对象。
-
创建迭代器并开始迭代:
let iterator = obj[Symbol.iterator]();
: 获取obj
的迭代器。let res = iterator.next();
: 调用迭代器的next
方法以获取第一个迭代结果。
-
迭代过程:
while (!res.done) {
: 只要迭代结果的done
属性为false
,循环将继续。cb(res.value);
: 调用回调函数cb
并传递当前迭代项的值。res = iterator.next();
: 获取下一个迭代结果。
function forOf(obj, cb) {
if (obj[Symbol.iterator]) {
throw new TypeError(obj + " is not iterable")
}
let iterator = obj[Symbol.iterator]()
let res = iterator.next()
while (!res.done) {
cb(res.value)
res = iterator.next()
}
}
var colors = ['red', 'green', 'blue']
forOf(colors, function(value) {
console.log(value);
})
五:字节面试题
让以下代码成立:var [a, b] = {a: 1, b: 2}
在这段代码中,{a: 1, b: 2}
是一个普通的 JS 对象。在默认情况下,对象不能被解构为变量列表。也就是说,var [a, b] = {a: 1, b: 2};
这样的写法会导致语法错误。为了使其成立,我们需要使对象变得可迭代,并且确保迭代顺序符合我们的期望。一种方法是定义对象的 Symbol.iterator
方法,让它返回一个迭代器,并且该迭代器可以按照我们期望的顺序返回对象的值。
解答:
Object.prototype[Symbol.iterator] = function() {
return Object.values(this)[Symbol.iterator]()
}
var [a, b] = {a: 1, b: 2}
解析:
将 Object.prototype
的 Symbol.iterator
属性定义为一个函数。该函数返回调用 Object.values(this)
所得数组的迭代器。这里的关键点是:
Object.values(this)
返回一个包含对象自身所有可枚举属性值的数组。[Symbol.iterator]()
调用返回的数组的迭代器。
这意味着现在所有的对象都可以通过 for...of
循环或其他迭代机制进行迭代。
转载自:https://juejin.cn/post/7401408756221542426