JavaScript 对象基础

对象是 JavaScript 中最核心的数据结构——几乎所有东西都是对象,或者可以表现得像对象。对象是**键值对(key-value)**的集合。

创建对象

javascript
// 对象字面量(推荐)
const user = {
  name: '张三',
  age: 25,
  city: '上海',
};

// 构造函数(少用)
const user2 = new Object();
user2.name = '李四';

访问属性

点号(.

javascript
console.log(user.name); // "张三"
console.log(user.age);  // 25

方括号([]

javascript
console.log(user['name']); // "张三"

// 方括号支持动态属性名
const key = 'city';
console.log(user[key]); // "上海"

// 属性名包含特殊字符时必须用方括号
const data = { 'user-id': 42 };
console.log(data['user-id']); // 42

动态访问属性(属性名来自变量)时只能用方括号。其余情况优先用点号——它更简洁,编辑器还能给出自动补全。

增删改属性

javascript
const user = { name: '张三' };

// 添加
user.age = 25;
user['city'] = '上海';

// 修改
user.name = '李四';

// 删除
delete user.age;

// 检查属性是否存在
console.log('name' in user);     // true
console.log(user.hasOwnProperty('name')); // true
console.log(user.age === undefined); // true(属性不存在)

对象方法

属性的值可以是函数——这种函数称为方法

javascript
const user = {
  name: '张三',
  greet() { // 简写语法(推荐)
    console.log(`你好,我是${this.name}`);
  },
  // 等价于
  // greet: function() { console.log(`你好,我是${this.name}`); }
};

user.greet(); // "你好,我是张三"

this 在方法中

方法内的 this 指向调用该方法的对象

javascript
const user = {
  name: '张三',
  greet() {
    console.log(this.name);
  },
};

user.greet(); // "张三"(this 指向 user)

const fn = user.greet;
fn(); // ❌ TypeError(严格模式下 this 为 undefined,this.name 报错)
// 非严格模式下输出 window.name(通常为空字符串)

this 的值取决于函数如何被调用,而不是函数在哪里定义。把对象方法单独拿出来作为变量调用时,this 会丢失对原对象的引用——这是 JavaScript 中最常见的困惑点之一。

遍历对象

javascript
const user = { name: '张三', age: 25, city: '上海' };

// 键名
Object.keys(user);     // ["name", "age", "city"]

// 键值
Object.values(user);   // ["张三", 25, "上海"]

// 键值对
Object.entries(user);  // [["name","张三"], ["age",25], ["city","上海"]]

// 配合 for...of 遍历
for (const [key, value] of Object.entries(user)) {
  console.log(`${key}: ${value}`);
}

对象解构

ES6 的解构赋值将对象属性直接提取到变量中:

javascript
const user = { name: '张三', age: 25, city: '上海' };

// 解构
const { name, age } = user;
console.log(name); // "张三"
console.log(age);  // 25

// 重命名
const { name: userName, age: userAge } = user;
console.log(userName); // "张三"

// 默认值
const { role = '普通用户' } = user;
console.log(role); // "普通用户"(user 中没有 role 属性)

// 剩余属性
const { name, ...rest } = user;
console.log(rest); // { age: 25, city: '上海' }

展开运算符 ...

javascript
// 复制对象(浅拷贝)
const copy = { ...user };

// 合并对象
const base = { name: '张三', role: '用户' };
const extra = { age: 25, city: '上海' };
const merged = { ...base, ...extra };
// { name: '张三', role: '用户', age: 25, city: '上海' }

// 覆盖属性(后面的覆盖前面的)
const updated = { ...user, age: 26 };
// { name: '张三', age: 26, city: '上海' }

引用类型陷阱

对象是按引用传递的,不是按值:

javascript
const a = { value: 10 };
const b = a; // b 指向 a 的同一个对象

b.value = 20;
console.log(a.value); // 20!a 也被修改了

要创建独立的副本,使用展开运算符或结构化克隆:

javascript
const a = { value: 10 };
const b = { ...a }; // 浅拷贝,创建独立对象

b.value = 20;
console.log(a.value); // 10(a 不受影响)

展开运算符做的是浅拷贝——只复制第一层属性。如果对象里有嵌套的对象或数组,内层仍然是引用。深拷贝(完全独立的副本)需要使用 structuredClone() 或递归实现。