JavaScript 循环与对象:深入理解 for、for...in、for...of、不可枚举属性与可迭代对象

在 JavaScript 的世界里,有多种方式可以遍历数据和操作对象。本文将深入探讨 for 循环、for...infor...of 三种循环的区别,并介绍如何创建具有特殊行为(如不可枚举属性)的对象,以及如何自定义可迭代对象,让你的代码更加灵活和强大。

1. 循环的演变:forfor...infor...of

这三种循环各自有不同的设计初衷和最佳应用场景,了解它们的差异对于写出高效且健壮的代码至关重要。

a. for 循环:最传统的遍历方式

  • 功能: 通过手动控制初始化、条件和迭代器,提供对循环过程最细粒度的控制。
  • 迭代目标: 通常用于遍历数组,通过索引访问元素。
  • 迭代内容 : 循环变量是数组的索引
  • 最佳实践 : 当你需要精确控制循环的开始、结束、步长,或在循环中频繁操作索引时,for 循环是最佳选择。在处理大型数组时,其性能通常优于其他循环。
javascript 复制代码
const arr = ['苹果', '香蕉', '橙子'];
for (let i = 0; i < arr.length; i++) {
  console.log(`索引 ${i} 的值是 ${arr[i]}`);
}
// 输出:
// 索引 0 的值是 苹果
// 索引 1 的值是 香蕉
// 索引 2 的值是 橙子

b. for...in:遍历对象的键

  • 功能 : 遍历一个对象所有可枚举的字符串属性,包括原型链上的属性。
  • 迭代目标 : 主要用于对象
  • 迭代内容 : 循环变量是对象的键(属性名)
  • 重要提示 : 不推荐用于遍历数组 。由于其会遍历原型链,且遍历顺序不确定,可能导致不可预测的行为。若需要遍历对象自身的属性,应配合 hasOwnProperty() 方法进行过滤。
javascript 复制代码
const obj = { name: 'Alice', age: 30 };
for (const key in obj) {
  if (Object.prototype.hasOwnProperty.call(obj, key)) {
    console.log(`${key}: ${obj[key]}`);
  }
}
// 输出:
// name: Alice
// age: 30

c. for...of:遍历可迭代对象的值

  • 功能 : 遍历可迭代对象 (Iterable Object)的 。这是 ES6 引入的现代循环方式,旨在解决 for...in 遍历数组时的弊端。
  • 迭代目标 : 适用于数组、字符串、MapSet 等所有可迭代对象。
  • 迭代内容 : 循环变量是可迭代对象的
  • 优点 : 语法简洁,直接访问值,并且支持 breakcontinue 控制流。
javascript 复制代码
const arr = ['苹果', '香蕉', '橙子'];
for (const value of arr) {
  console.log(value);
}
// 输出:
// 苹果
// 香蕉
// 橙子

const str = "hello";
for (const char of str) {
  console.log(char);
}
// 输出:
// h
// e
// l
// l
// o

2. 精确控制属性:创建不可枚举属性

在某些场景下,我们希望给对象添加一些内部使用的属性,但又不想让它们在常规遍历中暴露。这时,可以使用 Object.defineProperty() 方法来创建**不可枚举(non-enumerable)**属性。

使用 Object.defineProperty()

Object.defineProperty() 允许你精确地配置属性的特性,包括其可枚举性(enumerable)、可写性(writable)和可配置性(configurable)。

javascript 复制代码
const user = {
  name: 'Alice',
  age: 30
};

// 使用 Object.defineProperty() 添加一个不可枚举的属性 'id'
Object.defineProperty(user, 'id', {
  value: 12345,        // 属性的值
  writable: false,     // 不可被重新赋值
  enumerable: false,   // 不可被枚举(例如:for...in, Object.keys())
  configurable: false  // 不可被删除或更改特性
});

// 验证不可枚举性
for (const key in user) {
  console.log(key); // 输出: 'name', 'age'。忽略了 'id'。
}
console.log(Object.keys(user)); // 输出: ['name', 'age']。忽略了 'id'。
console.log(user.id); // 输出: 12345。仍然可以通过点或方括号正常访问。

3. 自定义迭代行为:创建可迭代对象

要使一个自定义对象能够被 for...of 循环遍历,你需要让它成为一个可迭代对象(Iterable) 。这意味着你需要在对象上实现一个 Symbol.iterator 方法,该方法返回一个符合迭代器协议的对象。

方法一:使用常规函数

手动实现 [Symbol.iterator] 方法,并返回一个带有 next() 方法的对象。

javascript 复制代码
const myCustomObject = {
  data: ['一', '二', '三'],
  [Symbol.iterator]: function() {
    let index = 0;
    const data = this.data;
    return {
      next: function() {
        if (index < data.length) {
          return { value: data[index++], done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

for (const item of myCustomObject) {
  console.log(item);
}
// 输出:
// 一
// 二
// 三

方法二:使用生成器函数(更简洁)

生成器函数(function*)是创建迭代器的更现代、更简洁的方法。yield 关键字会自动为你管理迭代状态。

javascript 复制代码
const myCustomObject = {
  data: ['红', '黄', '蓝'],
  *[Symbol.iterator]() {
    for (const item of this.data) {
      yield item;
    }
  }
};

for (const color of myCustomObject) {
  console.log(color);
}
// 输出:
// 红
// 黄
// 蓝
相关推荐
xump6 小时前
如何在DevTools选中调试一个实时交互才能显示的元素样式
前端·javascript·css
折翅嘀皇虫6 小时前
fastdds.type_propagation 详解
java·服务器·前端
Front_Yue6 小时前
深入探究跨域请求及其解决方案
前端·javascript
wordbaby6 小时前
React Native 进阶实战:基于 Server-Driven UI 的动态表单架构设计
前端·react native·react.js
抱琴_6 小时前
【Vue3】我用 Vue 封装了个 ECharts Hooks,同事看了直接拿去复用
前端·vue.js
风止何安啊6 小时前
JS 里的 “变量租房记”:闭包是咋把变量 “扣” 下来的?
前端·javascript·node.js
Danny_FD6 小时前
用 ECharts markLine 标注节假日
前端·echarts
程序员西西6 小时前
SpringBoot无感刷新Token实战指南
java·开发语言·前端·后端·计算机·程序员
烛阴6 小时前
Luban集成CocosCreator完整教程
前端·typescript·cocos creator
有点笨的蛋6 小时前
深入理解 JavaScript 原型机制:构造函数、原型对象与原型链
前端·javascript