对象基础

对象属性的定义、访问与修改

对象作为 JavaScript 中存储键值对数据的核心结构,其属性操作是基础且关键的能力。我们以用户信息对象为案例,系统讲解属性的定义方式、访问方法及修改规则。

对象字面量定义是最常用的创建方式,通过键值对直接声明对象。ES6 引入了属性初始化简写、方法简写和可计算属性名等语法糖,大幅简化了对象定义过程:

javascript
复制代码
// ES5 传统写法
let es5User = {
  name: 'Alice',
  age: 28,
  greet: function() {
    console.log(`Hello, ${this.name}`);
  }
};

// ES6 简写语法
let name = 'Bob';
let age = 30;
let key = 'role';
let es6User = {
  name,  // 属性初始化简写(等同于 name: name)
  age,
  greet() {  // 方法简写(省略 function 关键字)
    console.log(`Hello, ${this.name}`);
  },
  [key]: 'user'  // 可计算属性名(方括号内为表达式)
};

属性访问支持两种方式:点表示法obj.property)和方括号表示法obj['property']),后者适用于属性名包含特殊字符或动态生成的场景。属性修改通过直接赋值实现,如 es6User.age = 31 即可更新年龄属性。

只读属性的实现:Object.defineProperty

若需限制属性被修改,可使用 Object.defineProperty 方法精细控制属性特性。该方法允许定义属性的 writable(可写性)、enumerable(可枚举性)、configurable(可配置性)等高级特性。以下示例为用户对象添加只读 id 属性:

javascript
复制代码
let user = {
  name: 'Charlie',
  age: 25
};

// 定义只读 id 属性
Object.defineProperty(user, 'id', {
  value: 'u12345',  // 属性值
  writable: false,   // 设为 false 后属性不可修改
  enumerable: true,  // 设为 true 可被枚举(如 for...in 遍历)
  configurable: false // 设为 false 后不可删除或重新定义特性
});

console.log(user.id); // 输出:u12345
user.id = 'u67890';  // 尝试修改只读属性,严格模式下会报错
console.log(user.id); // 输出:u12345(值未改变)

注意Object.defineProperty 定义的属性默认特性为 writable: falseenumerable: falseconfigurable: false,而直接赋值定义的属性默认特性为 true。如需属性可被遍历或修改,需显式设置对应特性。

对象遍历:for...in 与 Object.keys 的差异

遍历对象属性是日常开发的常见需求,for...in 循环与 Object.keys() 方法是两种主要方式,但存在显著差异:

特性 for...in 循环 Object.keys(obj)
遍历范围 自身及继承的可枚举属性 自身可枚举属性
返回值 无返回值,需手动收集属性 返回属性名组成的数组
原型链污染风险 可能遍历到原型链上的意外属性 仅遍历对象自身属性,无原型链风险
典型用途 需遍历所有可枚举属性(含继承)时使用 仅需处理对象自身属性时使用

示例对比

javascript
复制代码
// 给 Object 原型添加自定义属性(模拟原型链污染)
Object.prototype.customProp = 'inherited';

let user = {
  name: 'Diana',
  age: 28
};

// 使用 for...in 遍历(会包含继承的 customProp)
console.log('for...in 遍历结果:');
for (let key in user) {
  console.log(key); // 输出:name, age, customProp
}

// 使用 Object.keys 遍历(仅包含自身属性)
console.log('Object.keys 遍历结果:', Object.keys(user)); // 输出:[ 'name', 'age' ]

为避免 for...in 遍历到继承属性,通常需配合 hasOwnProperty 方法过滤:

javascript
复制代码
for (let key in user) {
  if (user.hasOwnProperty(key)) { // 仅处理自身属性
    console.log(key); // 输出:name, age
  }
}

高效分组:Object.groupBy 与传统 reduce 对比

ES2024 新增的 Object.groupBy 方法为数组分组提供了极简方案,其接收可迭代对象分组回调函数,直接返回按指定键分组的对象。相比传统 reduce 方法,代码量减少 60% 以上,可读性显著提升。

场景:将用户数组按 role 属性分组(如 'admin'、'user'、'guest')。

传统 reduce 实现(需手动初始化累加器、处理属性不存在情况):

javascript
复制代码
const users = [
  { name: 'Alice', role: 'admin' },
  { name: 'Bob', role: 'user' },
  { name: 'Charlie', role: 'user' },
  { name: 'Diana', role: 'guest' }
];

const groupedByRole = users.reduce((acc, user) => {
  // 若分组键不存在,初始化为空数组
  acc[user.role] = acc[user.role] || [];
  // 将用户添加到对应分组
  acc[user.role].push(user);
  return acc;
}, {}); // 初始累加器为空对象

Object.groupBy 实现(一行代码完成分组):

javascript
复制代码
const groupedByRole = Object.groupBy(users, user => user.role);

两种方式均返回相同结果:

javascript
复制代码
{
  admin: [{ name: 'Alice', role: 'admin' }],
  user: [{ name: 'Bob', role: 'user' }, { name: 'Charlie', role: 'user' }],
  guest: [{ name: 'Diana', role: 'guest' }]
}
plaintext
复制代码
{
  "legend": {
    "bottom": 0,
    "data": [
      "用户数量"
    ],
    "textStyle": {
      "fontSize": 16
    }
  },
  "series": [
    {
      "data": [
        1,
        2,
        1
      ],
      "name": "用户数量",
      "type": "bar"
    }
  ],
  "title": {
    "left": "center",
    "text": "用户角色分布对比",
    "textStyle": {
      "fontSize": 20
    }
  },
  "tooltip": {
    "trigger": "item"
  },
  "xAxis": {
    "data": [
      "admin",
      "user",
      "guest"
    ],
    "type": "category"
  },
  "yAxis": {
    "name": "用户数量",
    "type": "value"
  }
}

Object.groupBy 的回调函数需返回字符串类型的分组键,其内部自动处理分组初始化逻辑,大幅降低了手动实现的出错风险。该方法已被主流浏览器支持,是处理分组场景的首选方案。

总结

对象基础操作是 JavaScript 编程的核心能力,需重点掌握:

  • 属性定义:灵活运用对象字面量简写语法,提升代码简洁性;
  • 只读控制:通过 Object.defineProperty 精细管理属性可写性;
  • 安全遍历:优先使用 Object.keys 避免原型链属性干扰,必要时配合 hasOwnProperty
  • 高效分组:采用 Object.groupBy 替代传统 reduce,简化分组逻辑。

这些技能将为后续复杂对象操作(如原型链、类与继承)奠定坚实基础。