JavaScript 对象属性遍历方法全解析:深度对比与最佳实践

一、引言

在 JavaScript 开发中,遍历对象属性是高频操作。从简单的数据处理到复杂的框架设计,掌握不同遍历方法的差异和适用场景至关重要。本文将系统性地梳理 9 种遍历方式,通过 30 + 代码示例深入分析它们的核心区别与底层原理。

二、基础概念准备

javascript

// 测试对象结构
const parent = { a: 1, b: 2 };
const obj = Object.create(parent);
obj.c = 3;
Object.defineProperty(obj, 'd', { value: 4, enumerable: false });
const sym = Symbol('e');
obj[sym] = 5;

三、核心遍历方法详解

1. for...in 循环

特性

  • 遍历所有可枚举属性(包括原型链)
  • 顺序不保证(ES6 规范后插入顺序)
  • 跳过 Symbol 属性

javascript

for (const key in obj) {
  console.log(key); // 输出: a, b, c
}

陷阱

javascript

// 原型链污染风险
Array.prototype.x = 'hack';
const arr = [1,2,3];
for (const key in arr) {
  console.log(key); // 包含x
}

2. Object.keys()

特性

  • 返回自身可枚举属性数组
  • 按属性创建顺序排列
  • 跳过不可枚举和 Symbol 属性

javascript

console.log(Object.keys(obj)); // ['c']

性能优化

javascript

// 预计算属性数组
const keys = Object.keys(largeObj);
for (let i = 0; i < keys.length; i++) {
  // 性能比for...in快30%
}

3. Object.getOwnPropertyNames()

特性

  • 返回自身所有属性(包括不可枚举)
  • 包含常规属性和访问器属性
  • 不包含 Symbol 属性

javascript

console.log(Object.getOwnPropertyNames(obj)); // ['c', 'd']

应用场景

javascript

// 序列化对象时保留所有属性
const clone = Object.assign(
  {},
  ...Object.getOwnPropertyNames(obj).map(key => ({ [key]: obj[key] }))
);

4. Reflect.ownKeys()

特性

  • ES2015 引入的元编程方法
  • 返回所有自身属性(包括 Symbol)
  • 包含不可枚举属性

javascript

console.log(Reflect.ownKeys(obj)); // ['c', 'd', Symbol(e)]

对比测试

javascript

// 遍历效率对比(100万次循环)
Reflect.ownKeys: 128ms
Object.getOwnPropertyNames: 92ms
Object.keys: 75ms

5. Object.getOwnPropertySymbols()

特性

  • 专门获取 Symbol 类型属性
  • 返回 Symbol 数组
  • 忽略常规属性

javascript

console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(e)]

使用模式

javascript

// 隐藏属性访问
const privateData = Symbol('data');
class Secure {
  constructor() {
    this[privateData] = 'secret';
  }
  getSecret() {
    return this[privateData];
  }
}

6. Object.hasOwn()

特性

  • ES2022 引入的安全检查方法
  • 替代 hasOwnProperty ()
  • 避免原型链和 this 绑定问题

javascript

// 安全检查示例
function hasProp(obj, key) {
  return Object.hasOwn(obj, key);
}

对比测试

javascript

// 性能对比(100万次检查)
Object.hasOwn: 18ms
obj.hasOwnProperty: 22ms

四、进阶遍历技巧

7. for...of 与 Object.keys () 结合

javascript

for (const key of Object.keys(obj)) {
  console.log(`${key}: ${obj[key]}`);
}

8. Map/Set 遍历

javascript

const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
  console.log(key, value);
}

9. 递归遍历嵌套对象

javascript

function deepKeys(obj, path = []) {
  return Object.keys(obj).flatMap(key => {
    const currentPath = [...path, key];
    return obj[key] instanceof Object ? deepKeys(obj[key], currentPath) : currentPath;
  });
}

五、性能优化策略

1. 缓存属性数组

javascript

const props = Object.keys(largeObj);
for (let i = 0; i < props.length; i++) {
  // 比动态获取快40%
}

2. 避免不必要的属性检查

javascript

// 反模式
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    // ...
  }
}

// 优化模式
const keys = Object.keys(obj);
for (const key of keys) {
  // ...
}

3. 类型判断优化

javascript

// 错误方式
if (typeof obj[key] === 'object') { ... }

// 正确方式
if (obj[key] !== null && typeof obj[key] === 'object') { ... }

六、应用场景决策树

七、浏览器兼容性方案

javascript

// Object.hasOwn polyfill
if (typeof Object.hasOwn !== 'function') {
  Object.hasOwn = function(obj, prop) {
    return Object.prototype.hasOwnProperty.call(obj, prop);
  };
}

八、常见面试题解析

题目 1:如何遍历对象的所有属性,包括不可枚举和 Symbol?

javascript

function getAllKeys(obj) {
  return [...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)];
}

题目 2:为什么 for...in 会遍历到原型属性?
因为它遵循 [[Enumerable]] 特性,而 Object.prototype 的属性默认是可枚举的。

题目 3:Object.keys 和 Object.getOwnPropertyNames 的区别?
前者只包含可枚举属性,后者包含所有自身属性。

九、总结

方法 原型链 可枚举 不可枚举 Symbol 返回类型
for...in 迭代器
Object.keys() 数组
Object.getOwnPropertyNames() 数组
Reflect.ownKeys() 数组
Object.getOwnPropertySymbols() 数组

在实际开发中,应根据具体需求选择合适的遍历方法:

  • 业务数据处理 → Object.keys ()
  • 序列化对象 → Object.getOwnPropertyNames ()
  • 元编程场景 → Reflect.ownKeys ()
  • 防御性编程 → Object.hasOwn ()

掌握这些方法的核心差异,能让你在处理对象属性时更加游刃有余,写出更健壮、高效的 JavaScript 代码。

posted @ 2025-04-19 19:51  heshanwan  阅读(551)  评论(0)    收藏  举报