如何使用for...of遍历对象

虽然普通对象默认不支持 for...of,但有多种方法可以实现:

1. 使用 Object 的辅助方法

遍历键名 (keys)

js 复制代码
const obj = { a: 1, b: 2, c: 3 };

// 方法1: Object.keys()
for (const key of Object.keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
  console.log(obj[key]); // 1, 2, 3
}

// 方法2: 使用数组的解构和循环
for (const key of Object.keys(obj)) {
  const value = obj[key];
  console.log(key, value);
}

遍历值 (values)

js 复制代码
const obj = { a: 1, b: 2, c: 3 };

for (const value of Object.values(obj)) {
  console.log(value); // 1, 2, 3
}

遍历键值对 (entries)

js 复制代码
const obj = { a: 1, b: 2, c: 3 };

// 方法1: 使用数组解构
for (const [key, value] of Object.entries(obj)) {
  console.log(key, value); // 'a' 1, 'b' 2, 'c' 3
}

// 方法2: 不使用解构
for (const entry of Object.entries(obj)) {
  const key = entry[0];
  const value = entry[1];
  console.log(key, value);
}

2. 使对象本身可迭代 (实现 Symbol.iterator)

基本实现

js 复制代码
const obj = {
  name: 'John',
  age: 30,
  city: 'New York',
  
  // 添加 Symbol.iterator 方法
  [Symbol.iterator]: function* () {
    // 遍历自身属性
    for (const key of Object.keys(this)) {
      yield [key, this[key]];
    }
  }
};

// 现在可以用 for...of 直接遍历
for (const [key, value] of obj) {
  console.log(`${key}: ${value}`);
}
// 输出:
// name: John
// age: 30
// city: New York

只返回值的迭代器

js 复制代码
const obj = {
  a: 1,
  b: 2,
  c: 3,
  [Symbol.iterator]: function* () {
    for (const key of Object.keys(this)) {
      yield this[key];
    }
  }
};

for (const value of obj) {
  console.log(value); // 1, 2, 3
}

3. 自定义迭代行为

按特定顺序迭代

js 复制代码
const user = {
  firstName: 'John',
  lastName: 'Doe',
  age: 30,
  email: 'john@example.com',
  
  [Symbol.iterator]: function* () {
    // 自定义迭代顺序
    yield ['姓名', `${this.firstName} ${this.lastName}`];
    yield ['年龄', this.age];
    yield ['邮箱', this.email];
  }
};

for (const [label, value] of user) {
  console.log(`${label}: ${value}`);
}
// 输出:
// 姓名: John Doe
// 年龄: 30
// 邮箱: john@example.com

只迭代特定属性

js 复制代码
const product = {
  id: 1,
  name: 'Laptop',
  price: 999.99,
  category: 'Electronics',
  inStock: true,
  
  [Symbol.iterator]: function* () {
    // 只迭代非布尔值的属性
    const keys = Object.keys(this);
    for (const key of keys) {
      if (typeof this[key] !== 'boolean') {
        yield [key, this[key]];
      }
    }
  }
};

for (const [key, value] of product) {
  console.log(`${key}: ${value}`);
}
// 输出:
// id: 1
// name: Laptop
// price: 999.99
// category: Electronics

4. 类实例的可迭代性

js 复制代码
class UserCollection {
  constructor() {
    this.users = [];
  }
  
  add(user) {
    this.users.push(user);
  }
  
  // 使实例可迭代
  *[Symbol.iterator]() {
    for (const user of this.users) {
      yield user;
    }
  }
  
  // 或者返回键值对
  *entries() {
    for (let i = 0; i < this.users.length; i++) {
      yield [i, this.users[i]];
    }
  }
}

const collection = new UserCollection();
collection.add({ name: 'Alice', age: 25 });
collection.add({ name: 'Bob', age: 30 });

// 遍历用户
for (const user of collection) {
  console.log(user.name, user.age);
}

// 遍历带索引的用户
for (const [index, user] of collection.entries()) {
  console.log(index, user.name);
}

5. 使用生成器函数

js 复制代码
function* objectEntries(obj) {
  for (const key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}

const obj = { x: 10, y: 20, z: 30 };

for (const [key, value] of objectEntries(obj)) {
  console.log(key, value);
}

6. 实际应用场景

场景1:遍历配置对象

js 复制代码
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3,
  debug: false
};

// 配置迭代器
config[Symbol.iterator] = function* () {
  const keys = Object.keys(this).sort(); // 按字母排序
  for (const key of keys) {
    yield { key, value: this[key] };
  }
};

for (const { key, value } of config) {
  if (typeof value !== 'boolean') { // 过滤布尔值
    console.log(`配置 ${key} = ${value}`);
  }
}

场景2:处理嵌套对象

js 复制代码
const company = {
  name: 'TechCorp',
  departments: {
    engineering: { employees: 50, budget: 1000000 },
    sales: { employees: 20, budget: 500000 },
    marketing: { employees: 15, budget: 300000 }
  }
};

// 扁平化迭代器
company[Symbol.iterator] = function* () {
  yield ['公司名称', this.name];
  for (const [dept, info] of Object.entries(this.departments)) {
    yield [`${dept}部门人数`, info.employees];
    yield [`${dept}部门预算`, info.budget];
  }
};

for (const [label, value] of company) {
  console.log(`${label}: ${value}`);
}

7. 性能考虑

js 复制代码
const obj = {};
// 创建一个大对象
for (let i = 0; i < 1000000; i++) {
  obj[`key${i}`] = i;
}

// 测试不同方法的性能
console.time('Object.keys + for...of');
for (const key of Object.keys(obj)) {
  const value = obj[key];
}
console.timeEnd('Object.keys + for...of');

console.time('Object.entries + for...of');
for (const [key, value] of Object.entries(obj)) {
  // 直接有值
}
console.timeEnd('Object.entries + for...of');

console.time('for...in');
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    const value = obj[key];
  }
}
console.timeEnd('for...in');

8. 实用工具函数

js 复制代码
// 创建可迭代对象的工具函数
function makeIterable(obj, options = {}) {
  const {
    exclude = [],      // 排除的属性
    includeOnly = null, // 只包含的属性
    sortKeys = false,  // 是否按键排序
    valueOnly = false  // 只返回值
  } = options;
  
  const iterable = { ...obj };
  
  iterable[Symbol.iterator] = function* () {
    let keys = Object.keys(this);
    
    // 过滤
    if (exclude.length) {
      keys = keys.filter(key => !exclude.includes(key));
    }
    
    if (includeOnly) {
      keys = keys.filter(key => includeOnly.includes(key));
    }
    
    // 排序
    if (sortKeys) {
      keys.sort();
    }
    
    // 迭代
    for (const key of keys) {
      if (valueOnly) {
        yield this[key];
      } else {
        yield [key, this[key]];
      }
    }
  };
  
  return iterable;
}

// 使用示例
const data = { a: 1, b: 2, c: 3, d: 4, e: 5 };
const iterableData = makeIterable(data, {
  exclude: ['e'],
  sortKeys: true
});

for (const [key, value] of iterableData) {
  console.log(key, value); // a 1, b 2, c 3, d 4
}

总结

  1. 最简单的方法 :使用 Object.keys()Object.values()Object.entries()
  2. 需要直接迭代对象 :实现 Symbol.iterator 方法
  3. 需要自定义迭代逻辑:使用生成器函数
  4. 考虑性能Object.entries() + for...of 通常是性能和可读性的最佳平衡
js 复制代码
// 推荐的最佳实践
const obj = { a: 1, b: 2, c: 3 };

// 1. 只需要键或值
for (const key of Object.keys(obj)) { }
for (const value of Object.values(obj)) { }

// 2. 需要键值对
for (const [key, value] of Object.entries(obj)) { }

// 3. 需要直接迭代对象(添加迭代器)
obj[Symbol.iterator] = function* () {
  for (const [key, value] of Object.entries(this)) {
    yield { key, value };
  }
};
相关推荐
秋天的一阵风17 小时前
🎥解决前端 “复现难”:rrweb 录制回放从入门到精通(下)
前端·开源·全栈
林恒smileZAZ17 小时前
【Vue3】我用 Vue 封装了个 ECharts Hooks
前端·vue.js·echarts
颜酱17 小时前
用填充表格法-继续吃透完全背包及其变形
前端·后端·算法
代码猎人17 小时前
new操作符的实现原理是什么
前端
程序员小李白17 小时前
定位.轮播图详细解析
前端·css·html
前端小菜鸟也有人起17 小时前
浏览器不支持vue router
前端·javascript·vue.js
奔跑的web.17 小时前
Vue 事件系统核心:createInvoker 函数深度解析
开发语言·前端·javascript·vue.js
携欢17 小时前
[特殊字符] 一次经典Web漏洞复现:修改序列化对象直接提权为管理员(附完整步骤)
前端·安全·web安全
晨旭缘17 小时前
前端视角 | 从零搭建并启动若依后端(环境配置)
前端