for...in 和 for...of 这两个东西在 JavaScript 里都用来做循环,但它们用起来完全不是一回事。要是搞不清楚,代码很容易出问题。
咱们先说 for...in。这个循环是用来遍历一个对象的所有可枚举属性的,简单说就是拿出对象里所有的键(key)。
看这个例子,我们有一个普通的对象 person:
“`javascript
const person = {
name: ‘老王’,
age: 30,
job: ‘程序员’
};
for (const key in person) {
console.log(key);
}
“`
运行这段代码,你会看到控制台输出了 name、age 和 job。这就是 for...in 的作用,它把 person 对象里的每个键都拿出来,让你能用它们做点什么。如果你想同时拿到键和对应的值,可以这么写:
javascript
for (const key in person) {
console.log(key + ': ' + person[key]);
}
这样,输出就变成了:
name: 老王
age: 30
job: 程序员
看起来挺好用,对吧?但问题就出在,很多人会误以为它也能很好地处理数组。理论上,数组也是对象,它的键就是索引(0, 1, 2…),所以 for...in 确实能用在数组上。
比如下面这个数组:
“`javascript
const fruits = [‘苹果’, ‘香蕉’, ‘橘子’];
for (const index in fruits) {
console.log(index);
}
“`
控制台会输出 0、1、2。这些都是字符串,不是数字。这是第一个要注意的地方,for...in 拿到的键永远是字符串。如果你想用这个索引去做数学计算,还得先把它转成数字。
但这还不是最大的问题。for...in 最大的坑在于它会遍历原型链上的属性。JavaScript 的对象都有一个叫“原型”的东西,对象可以从原型上继承属性和方法。如果有人不小心修改了数组的原型,for...in 就会把那些不属于数组本身的属性也给遍历出来。
举个例子,假设有人在别的地方给所有数组加了一个新的方法:
“`javascript
Array.prototype.sayHello = function() {
console.log(‘你好’);
};
const fruits = [‘苹果’, ‘香蕉’, ‘橘子’];
for (const index in fruits) {
console.log(index);
}
“`
这时候再运行,输出结果就不只是 0、1、2 了,还会多一个 sayHello。这就完全不是我们想要的结果。在实际开发中,你可能根本不知道哪个库或者哪个同事动了原型,这个问题会变得很难排查。
另外,for...in 循环也不能保证遍历的顺序。虽然大部分现代浏览器会按照 0, 1, 2 的顺序来,但根据 JavaScript 的规范,这个顺序是不确定的。所以,如果你想按顺序处理数组里的元素,用 for...in 是一个很冒险的行为。
所以结论很简单:除非你非常确定自己要干什么,比如检查一个普通对象的内部构造,否则就别用 for...in 来遍历数组。
接下来说 for...of。这是在 ES6(ECMAScript 2015)里新加的循环方式,它的出现很大程度上就是为了解决 for...in 的那些问题。
for...of 是专门用来遍历可迭代对象(iterable objects)的值的。什么是可迭代对象?简单理解,就是那些内部实现了特定接口,可以让你按顺序一个一个拿出其中元素的对象。常见的可迭代对象包括数组、字符串、Map、Set 等。
我们用 for...of 来试试刚才那个水果数组:
“`javascript
const fruits = [‘苹果’, ‘香蕉’, ‘橘子’];
for (const fruit of fruits) {
console.log(fruit);
}
“`
这次,控制台输出的是 苹果、香蕉、橘子。看到了吗?它直接拿出了数组里的值,而不是索引。这才是我们通常想要的结果。
for...of 有几个非常好的优点:
1. 直接获取值:它拿到的是元素的值,不是键或索引,代码写起来更直接。
2. 不会遍历原型链:它只关心数据本身,就算你修改了 Array.prototype,for...of 也不会去管那些新增的属性或方法。
3. 保证顺序:对于数组、字符串这类有序的集合,for...of 会严格按照元素的顺序进行遍历。
4. 适用范围广:除了数组,它还能用在很多其他数据结构上。
比如字符串:
“`javascript
const message = ‘你好’;
for (const char of message) {
console.log(char);
}
“`
输出会是:
你
好
它把字符串里的每个字符都按顺序拿出来了。
但是,for...of 不能用在普通的对象上,因为普通的对象默认不是可迭代的。如果你非要这么做,程序会直接报错。
“`javascript
const person = {
name: ‘老王’,
age: 30
};
// 下面这行代码会报错: person is not iterable
for (const value of person) {
console.log(value);
}
“`
这一点也正好和 for...in 形成了互补。for...in 的主场是普通对象,而 for...of 的主场是数组、字符串这类可迭代的数据集合。
现在我们来总结一下它们的核心区别:
-
遍历的东西不同:
for...in遍历的是对象的键(key),而且这些键是字符串。for...of遍历的是可迭代对象的值(value)。
-
适用对象不同:
for...in可以用在任何对象上,但主要设计用来遍历普通对象的属性。for...of只能用在可迭代对象上,比如数组、字符串、Map、Set 等。
-
安全性和可靠性:
for...in会遍历原型链上的属性,并且不保证顺序,用在数组上风险很高。for...of不会受原型链影响,能保证遍历顺序,是遍历数组等有序集合的现代、安全的方式。
那么,在实际工作中到底该怎么选?
规则很简单:
-
如果你要遍历一个数组或者字符串,用
for...of。 这是最直接、最安全的选择。它能让你拿到每个元素的值,并且不用担心顺序和原型链污染的问题。 -
如果你要遍历一个普通对象的属性,用
for...in。 这是它的本职工作。不过,为了避免遍历到原型链上的属性,最好加上一个检查。“`javascript
const person = {
name: ‘老王’,
age: 30
};for (const key in person) {
if (Object.prototype.hasOwnProperty.call(person, key)) {
console.log(key + ‘: ‘ + person[key]);
}
}
“`hasOwnProperty.call()这段代码的作用就是检查这个属性是不是对象自己的,而不是从原型上继承来的。虽然写起来麻烦一点,但能让代码更健壮。
不过,现在处理对象还有更好的方法。比如,如果你只想拿到对象所有的键,可以用 Object.keys();如果只想拿到所有的值,可以用 Object.values();如果想同时拿到键和值,可以用 Object.entries()。这些方法都会返回一个数组,然后你就可以用 for...of 来处理了。
“`javascript
const person = {
name: ‘老王’,
age: 30
};
// 遍历键
for (const key of Object.keys(person)) {
console.log(key);
}
// 遍历值
for (const value of Object.values(person)) {
console.log(value);
}
// 遍历键和值
for (const [key, value] of Object.entries(person)) {
console.log(key + ‘: ‘ + value);
}
“`
用这种方式来处理对象,代码不仅更清晰,也避免了 for...in 的那些潜在问题。所以,现在很多人即使在处理普通对象时,也越来越少直接用 for...in 了。
总的来说,for...of 是现代 JavaScript 中处理循环遍历的主要方式,尤其是在和数组、字符串打交道时。而 for...in 则更像一个专门用来检查对象内部结构的工具,使用时需要格外小心。理解了这一点,就能在写代码时做出正确的选择。

技能提升网