for in 、for of 、forEach 区别

215 阅读5分钟

前言:前端的基础知识忘的差不多了,是时候拾起来看看啦,温故而知新

for in

在 JavaScript 中,for...in 循环用于遍历对象的可枚举属性。当用于数组时,它会遍历数组对象的每一个可枚举属性,包括数组的索引(即元素的键)以及数组对象可能拥有的其他属性。 然而,for...in 循环有几个重要的注意事项:

  • 不保证顺序 for...in 循环不保证按照任何特定的顺序来返回属性。在 ES5 及以前的 JavaScript 规范中,对象的属性遍历顺序是完全未定义的。从 ES6 开始,虽然对象的属性迭代顺序得到了一定的规范(例如,整数索引按升序排列,然后是其他字符串键,然后是符号键,最后是其他类型的键),但即使这样,for...in 循环的行为也没有改变,它仍然不应该被用于依赖顺序的数组遍历。
  • 包括原型链上的属性 for...in 循环不仅遍历对象自身的属性,还遍历其原型链上的属性。这可能导致意外的行为,特别是当使用内建对象或具有扩展原型的对象时

为什么for ...in不保证按照数组的顺序进行迭代?

结论先行: 在 JavaScript 中,for...in 循环用于遍历对象的可枚举属性,包括数组索引(数组在 JavaScript 中也是对象)。然而,for...in 循环并不保证属性被访问的顺序,这是因为 JavaScript 对象的属性没有特定的顺序。对于数组来说,虽然它们的索引看起来像是按顺序排列的,但使用 for...in 循环遍历数组时,实际上是在遍历数组对象的属性。由于 JavaScript 对象属性的迭代顺序是不确定的(至少在 ES5 及之前的规范中是这样),因此 for...in 循环在遍历数组时也不会按照索引的顺序来。

追溯根源

在大多数编程语言中,for...in 循环用于遍历集合(如数组、列表、字典、集合等)中的元素。然而,是否按照特定的顺序(如数组的插入顺序)进行迭代,这取决于语言的设计和实现。

对于某些语言(如 Python),for...in 循环通常按照集合中元素的插入顺序进行迭代。这是因为这些语言中的集合类型(如列表和元组)是有序的,它们保持了元素的插入顺序。

此外,即使对于保持插入顺序的集合类型,某些语言或库也可能提供选项来改变迭代顺序。例如,一些语言允许对数组或列表进行排序,然后使用 for...in 循环按照排序后的顺序进行迭代。

总之,for...in 循环是否按照数组的顺序进行迭代取决于所使用的编程语言、集合类型以及可能的选项和配置。

for of

在 JavaScript 中,for...of 循环是一个用于遍历可迭代对象(包括 Array,Map,Set,arguments 对象等等)的语法结构。它直接遍历可迭代对象的值,而不是键名(或索引),for ...of的特点:

  • 直接遍历可迭代对象的值
const array = [1, 2];  
for (const value of array) {  
   console.log(value);  
}  
// 输出:  
// 1  
// 2  
  • 不会遍历对象的原型链
function MyObject() {  
  this.property1 = 'foo';  
  this.property2 = 'bar';  
}  
  
MyObject.prototype.propertyFromPrototype = 'baz';  
  
const obj = new MyObject();  
  
// 使用 for...of 会报错,因为对象不是可迭代的  
// for (const value of obj) {  
//     console.log(value); // TypeError: obj is not iterable  
// }  
  
// 使用 for...in 会遍历对象自身的属性和原型链上的属性  
for (const prop in obj) {  
    console.log(prop); // 输出: property1, property2, propertyFromPrototype  
}  
  
for (const key of Object.keys(obj)) {  
    console.log(key, obj[key]); // 输出: property1 foo, property2 bar  
}  
  
for (const value of Object.values(obj)) {  
    console.log(value); // 输出: foo, bar  
}  
  
for (const [key, value] of Object.entries(obj)) {  
    console.log(key, value); // 输出: property1 foo, property2 bar
}

需要注意的是,for...of 循环只能用于可迭代对象。如果你尝试用它来遍历一个普通的对象(即不是数组、Map、Set 等可迭代对象),你会得到一个类型错误(TypeError)。如果你需要遍历对象的属性,你应该使用 for...in 循环或其他适当的方法,比如 Object.keys()Object.values(), 或 Object.entries()

forEach

在 JavaScript 中,forEach 是一个数组的方法,用于遍历数组的每个元素并执行提供的函数。forEach 方法会针对数组中的每个元素调用一次提供的函数。forEach的特点:

  • 遍历数组的每个元素
  • 不可被中断
  • 无返回值

需要注意的是:forEach() 期望的是一个同步函数,它不会等待 Promise 兑现。在使用 Promise(或异步函数)作为 forEach 回调时,请确保你意识到这一点可能带来的影响。

const ratings = [5, 4, 5];
let sum = 0;

const sumFunction = async (a, b) => a + b;

ratings.forEach(async (rating) => {
    sum = await sumFunction(sum, rating);
});

console.log(sum);
// 期望的输出:14
// 实际的输出:0

forEach 为什么不能被中断呢?

在 JavaScript 中确实不能被中断,这是由其设计决定的。forEach() 方法用于对数组的每个元素执行一次提供的函数,并且不返回任何值(或者说返回 undefined

forEach() 的设计初衷是遍历整个数组,并且不提供中断遍历的机制。如果你需要在遍历过程中中断,那么应该使用其他的方法,比如 for 循环、for...of 循环或者 Array.prototype.some()、Array.prototype.every()、Array.prototype.find() 等方法,它们提供了可以在特定条件下中断遍历的机制。