在 JavaScript 中,有许多不同的迭代方式可以帮助你遍历数据结构。在本文中,我将介绍三种不同的迭代方式:Iterator、for...of 循环和 for...in 循环。我们将讨论它们的区别,以及如何在代码中使用它们。
一、 for...in循环
1. 遍历对象
let obj = {
a: 1,
b: 2,
c: 3
}
for (const key in obj) {
console.log(key, obj[key]);
}
for...in可以遍历对象,其中key是对象属性的键名,obj[key]是键值。
let obj = {
a: 123
}
let obj2 = Object.create(obj)
obj2.b = 666
obj2.c = 888
console.log(obj2);
for (const key in obj2) {
console.log(key, obj2[key]);
}
在上面的代码示例中,输出obj2对象有b和c属性,在用for...in遍历它时,却还输出了属性a,这是因为我们使用Object.create()方法创建对象obj2,obj2会隐式继承到obj的a属性。for...in不仅可以遍历显示具有的属性,还可以遍历对象原型上的属性。
那么如果我们只想要obj2的显示属性,不要它隐式继承得到的属性呢?可以运用hasOwnProperty()方法。
for (const key in obj2) {
if(obj2.hasOwnProperty(key)) {
console.log(key, obj2[key]); //输出:b 666 c 888
}
}
hasOwnProperty()方法用来判断该属性是否是对象自身的属性(显示而非隐式具有的属性),可以用来过滤继承的属性。而且该函数的length为1,表示该函数只接收一个参数。
2. 遍历数组
Array.prototype.d = 'ddd'
const arr = ['a', 'b', 'c']
for (let item in arr) {
console.log(item); //输出:0 1 2 d
}
for (let item in arr) {
if(arr.hasOwnProperty(item)){
console.log(item); //输出:0 1 2
}
}
for...in遍历数组,输出的item是数组的下标,数组是可以使用hasOwnProperty()方法的,因为数组本身也是对象(object)创建出来的,它原型的原型上有hasOwnProperty()方法。
3. 遍历字符串
let str = 'Hello'
console.log(str);
for (const key in str) {
console.log(key + ': ' + str[key]);
}
for...in遍历字符串时,key是字符串的下标,obj[key]是下标对应的字符。和遍历数组时类似。
二、for...of循环
1. 遍历数组
let arr = [6,7,8,9]
for (const item of arr) {
console.log(item);
}
for...of遍历数组,得到的是数组的值,而不是下标。数组原生具备iterator接口(即默认部署了Symbol.iterator属性)。
Array.prototype.aaa = 'aaa'
现在,我们在数组原型上挂上属性aaa,可以看到其实数组arr上已经继承了aaa属性,但是为啥没有输出呢?因为for...of不能遍历到数组原型上的属性。
2. 遍历对象
let obj = {
a: 1,
b: 2,
c: 3
}
for (const item of obj) {
console.log(item);
}
当我们用for...of去遍历对象时,结果报错了,obj is not iterable,for...of循环本质上就是调用iterator接口产生的遍历器。for...of不能遍历不具有 iterator属性的数据结构。对象不具备iterator接口,所以这里报错。
三、Iterator (遍历器)
Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员),帮助我们遍历数据结构中的元素。它提供了一个next()方法,该方法返回数据结构中下一个元素的值和一个指示是否还有更多元素的布尔值。下面是一个简单的迭代器示例:
const myArray = [1, 2, 3];
const myIterator = myArray[Symbol.iterator]();
console.log(myIterator.next()); // {value: 1, done: false}
console.log(myIterator.next()); // {value: 2, done: false}
console.log(myIterator.next()); // {value: 3, done: false}
console.log(myIterator.next()); // {value: undefined, done: true}
在上面的代码示例中,我们创建了一个数组myArray,然后使用Symbol.iterator方法创建了一个迭代器 myIterator。接下来,我们调用了myIterator.next()方法三次,每次都返回了数据结构中的下一个元素的值和一个布尔值,指示是否还有更多元素。最后一次调用myIterator.next()返回了undefined和true,表示没有更多元素。接下来我们来看一道面试题:
let [a, b] = {a: 1, b: 2}
console.log(a, b);
请问如何让其正常输出,并且输出结果为1 2,显而易见的是这里如果直接输出是会报错的。
报错:TypeError: {(intermediate value)(intermediate value)} is not iterable,因为对象这种数据结构本无Iterator接口,无法遍历,但是我们想要让它能够遍历取键值,并把值赋给数组,怎么办呢?那就让它可遍历嘛!就是人为的给对象的原型上部署Iterator接口。
Object.prototype[Symbol.iterator] = function() {
return Object.values(this)[Symbol.iterator]()
}
let [a, b] = {a: 1, b: 2}
console.log(a, b); //输出:1 2
在上面的代码示例中,Symbol.iterator属性对应一个函数,Object构造函数身上有一个方法是Object.values(),它会以数组的形式返回键值,this指向实例对象,这样我们Object.values(this)得到的就是一个数组,最后调用[Symbol.iterator]()返回一个Array类型的可迭代对象。
四、最后的话
理解 Iterator、for...of 循环和 for...in 循环的工作原理,以及如何在代码中使用它们,是非常重要的。这些工具可以帮助我们更方便地遍历数据结构,从而更高效地处理数据。
感谢你阅读这篇文章,如果你觉得写得还行的话,不要忘记点赞、评论、收藏哦!能力一般,水平有限,如有问题欢迎大家指正,祝大家生活愉快!