JS 对象遍历全解析:从基础到实战,新手也能轻松上手
在 JavaScript 开发中,对象(Object)是最常用的数据结构之一,几乎所有业务场景都会涉及到对象的遍历------比如获取对象的所有属性、筛选符合条件的键值对、批量处理对象数据等。
但很多新手在面对对象遍历时,总会困惑:for...in 和 Object.keys() 有什么区别?什么时候用 Object.values()?如何遍历对象的原型链属性?
今天这篇文章,就带你彻底吃透 JS 对象遍历的所有常用方法,从基础用法到进阶技巧,再到实战场景对比,帮你避开坑点、灵活运用。
一、先明确:什么是 JS 对象?
在开始遍历之前,我们先简单回顾下 JS 对象的基础------对象是由 键(key) 和 值(value) 组成的无序集合,键通常是字符串(ES6 后支持 Symbol),值可以是任意数据类型(字符串、数字、函数、对象等)。
示例对象(后文所有方法均基于此对象演示):
// 基础对象
const user = {
name: "掘金小册",
age: 3,
gender: "male",
isVip: true,
hobbies: ["coding", "writing"],
// 方法
sayHello: function() {
console.log("Hello, Juejin!");
}
};
// 原型链上的属性(用于后续演示区别)
Object.prototype.protoProp = "我是原型链上的属性";
二、常用对象遍历方法(按使用频率排序)
以下方法是开发中最常用的,重点掌握前 4 种,基本能覆盖 90%+ 的业务场景。
- for...in 循环(最基础,遍历可枚举属性)
for...in是最早的对象遍历方法,也是最基础的方式,它会遍历对象自身的可枚举属性 和原型链上的可枚举属性 (这是它的坑点,也是重点注意事项)。
基础用法
// for...in 遍历对象
for (let key in user) {
console.log("键:", key); // 输出对象的键
console.log("值:", user[key]); // 输出对应的值
}
输出结果(重点看最后一行)
键: name,值: 掘金小册
键: age,值: 3
键: gender,值: male
键: isVip,值: true
键: hobbies,值: [ 'coding', 'writing' ]
键: sayHello,值: [Function: sayHello]
键: protoProp,值: 我是原型链上的属性
核心注意事项(避坑关键)
- 会遍历原型链上的可枚举属性(如上面的
protoProp),这通常不是我们想要的,所以一定要搭配hasOwnProperty()使用。 - 不能遍历 Symbol 类型的键(后文会讲专门遍历 Symbol 的方法)。
正确用法(搭配 hasOwnProperty)
for (let key in user) {
// 只遍历对象自身的属性,过滤原型链属性
if (user.hasOwnProperty(key)) {
console.log("键:", key, "值:", user[key]);
}
}
这样就不会输出原型链上的 protoProp 了,这是 for...in 的标准用法。
- Object.keys() + forEach(最常用,遍历自身可枚举键)
ES5 新增的 Object.keys() 方法,会返回一个包含对象自身可枚举属性键 的数组(不包含原型链属性,也不包含 Symbol 键),再搭配 forEach 循环,是目前开发中最常用的遍历方式。
基础用法
// 1. 获取对象自身的所有可枚举键,返回数组
const keys = Object.keys(user);
console.log(keys); // 输出:['name', 'age', 'gender', 'isVip', 'hobbies', 'sayHello']
// 2. 搭配 forEach 遍历
Object.keys(user).forEach(key => {
console.log("键:", key);
console.log("值:", user[key]);
});
核心优势
- 自动过滤原型链属性,无需手动写
hasOwnProperty(),更简洁、更安全。 - 返回的是数组,可直接使用数组的方法(如
forEach、map、filter等),灵活度更高。
实战场景
筛选对象中值为布尔类型的键值对:
const boolProps = {};
Object.keys(user).forEach(key => {
if (typeof user[key] === "boolean") {
boolProps[key] = user[key];
}
});
console.log(boolProps); // 输出:{ isVip: true }
- Object.values() + forEach(遍历自身可枚举值)
ES2017(ES8)新增的 Object.values() 方法,和 Object.keys() 对应,它会返回一个包含对象自身可枚举属性值 的数组(不包含原型链属性、Symbol 键对应的值)。
基础用法
// 获取对象自身的所有可枚举值,返回数组
const values = Object.values(user);
console.log(values);
// 输出:['掘金小册', 3, 'male', true, ['coding', 'writing'], [Function: sayHello]]
// 搭配 forEach 遍历值
Object.values(user).forEach(value => {
console.log("值:", value);
});
实战场景
统计对象中所有数值类型的值的总和:
const sum = Object.values(user).reduce((total, value) => {
// 只累加数值类型的值
return typeof value === "number" ? total + value : total;
}, 0);
console.log(sum); // 输出:3
- Object.entries() + forEach(遍历自身可枚举键值对)
同样是 ES2017 新增的方法,Object.entries() 是最灵活的遍历方法,它会返回一个包含对象自身可枚举键值对 的二维数组(每个子数组是 [key, value]),完美兼顾键和值的获取。
基础用法
// 获取对象自身的所有可枚举键值对,返回二维数组
const entries = Object.entries(user);
console.log(entries);
// 输出:[
// ['name', '掘金小册'],
// ['age', 3],
// ...
// ]
// 搭配 forEach 遍历键值对
Object.entries(user).forEach(([key, value]) => {
// 解构赋值,直接获取 key 和 value,更简洁
console.log(`键:${key},值:${value}`);
});
实战场景
将对象转换为 Map(Map 支持更多灵活操作):
// Object.entries() 可直接作为 Map 的构造参数
const userMap = new Map(Object.entries(user));
console.log(userMap.get("name")); // 输出:掘金小册
// 遍历 Map(补充)
userMap.forEach((value, key) => {
console.log("键:", key, "值:", value);
});
- 进阶:遍历 Symbol 类型的键
如果对象的键是 Symbol 类型(ES6 新增,用于表示唯一键),上面的 4 种方法都无法遍历到,此时需要使用 Object.getOwnPropertySymbols() 方法。
示例用法
// 定义一个包含 Symbol 键的对象
const obj = {
[Symbol("id")]: 123,
name: "测试"
};
// 遍历 Symbol 类型的键
Object.getOwnPropertySymbols(obj).forEach(symbolKey => {
console.log("Symbol 键:", symbolKey);
console.log("对应值:", obj[symbolKey]); // 输出:123
});
- 高阶:遍历所有属性(自身+原型链,可枚举+不可枚举)
开发中很少用到,但面试可能会问------如果需要遍历对象自身的所有属性 (包括不可枚举),或者原型链上的所有属性,可以使用以下方法:
Object.getOwnPropertyNames(obj):返回对象自身的所有属性键(包括不可枚举,不包括 Symbol)。Reflect.ownKeys(obj):返回对象自身的所有属性键(包括不可枚举、Symbol 键),相当于Object.keys(obj) + Object.getOwnPropertySymbols(obj) + 不可枚举键。
示例
// 遍历自身所有属性(包括不可枚举)
const allOwnKeys = Object.getOwnPropertyNames(user);
console.log(allOwnKeys); // 包含 sayHello(函数也是属性)
// 遍历自身所有属性(包括不可枚举、Symbol)
const allKeys = Reflect.ownKeys(user);
console.log(allKeys);
三、常用方法对比表(一目了然)
| 方法 | 遍历范围 | 是否包含原型链 | 是否包含 Symbol 键 | 是否包含不可枚举 | 适用场景 |
|---|---|---|---|---|---|
| for...in | 自身可枚举 + 原型链可枚举 | 是(需手动过滤) | 否 | 否 | 兼容旧环境,需过滤原型链 |
| Object.keys() | 自身可枚举键 | 否 | 否 | 否 | 仅需获取键,搭配数组方法 |
| Object.values() | 自身可枚举值 | 否 | 否 | 否 | 仅需获取值,如统计、筛选 |
| Object.entries() | 自身可枚举键值对 | 否 | 否 | 否 | 需同时操作键和值(最常用) |
| Object.getOwnPropertySymbols() | 自身 Symbol 键 | 否 | 是 | 否 | 遍历 Symbol 类型的键 |
| Reflect.ownKeys() | 自身所有键(可枚举+不可枚举+Symbol) | 否 | 是 | 是 | 高阶场景,需获取所有自身属性 |
四、避坑指南(新手必看)
- 永远不要用
for...in遍历数组!虽然数组也是对象,但for...in会遍历数组的原型链属性,还会按照"字符串索引"排序,导致遍历顺序错乱。 - 使用
for...in必须搭配hasOwnProperty(),否则会遍历到原型链上的无关属性,导致业务逻辑出错。 - Symbol 类型的键无法被
Object.keys()、Object.values()、for...in遍历,需用专门的Object.getOwnPropertySymbols()。 - 对象是无序的!虽然 ES6 后对象的键会按照"数字优先、插入顺序"排列,但不要依赖对象的遍历顺序来实现业务逻辑(如需有序,建议用 Map)。
五、实战案例(综合运用)
需求:遍历一个用户对象,筛选出所有非函数类型的属性,将其转换为查询字符串(如:name=掘金小册&age=3)。
const user = {
name: "掘金小册",
age: 3,
gender: "male",
isVip: true,
sayHello: function() {
console.log("Hello");
}
};
// 1. 遍历对象,筛选非函数属性
const queryArr = Object.entries(user).filter(([key, value]) => {
// 排除函数类型的属性
return typeof value !== "function";
}).map(([key, value]) => {
// 将键值对转换为 "key=value" 格式
return `${key}=${encodeURIComponent(value)}`;
});
// 2. 拼接为查询字符串
const queryStr = queryArr.join("&");
console.log(queryStr);
// 输出:name=掘金小册&age=3&gender=male&isVip=true
解析:这里结合了 Object.entries()、filter()、map() 方法,既遍历了键值对,又完成了筛选和格式转换,是开发中非常典型的场景。
六、总结
JS 对象遍历的核心是"明确遍历范围"------你是要遍历自身属性,还是原型链属性?是要键、值,还是键值对?是包含 Symbol 或不可枚举属性?
对于绝大多数开发场景:
- 仅需键 → 用
Object.keys() - 仅需值 → 用
Object.values() - 需键值对 → 用
Object.entries()(最常用) - 兼容旧环境 → 用
for...in + hasOwnProperty()
最后,祝大家在 JS 的世界里,遍历无坑,编码无忧!🚀