Skip to content

JavaScript对象基础

对象是JavaScript中最重要的数据类型之一,几乎所有JavaScript中的值最终都是对象。对象是一种复合值,它可以将多个相关的值(属性)聚合在一起,形成一个整体。本文将详细介绍JavaScript中对象的基础概念、创建方法、属性操作和常用技巧。

对象概述

什么是对象

在JavaScript中,对象是一种复合数据类型,用来存储各种键值对(key-value pairs)。每个键值对被称为属性(property),其中键(key)是一个字符串(或Symbol),值(value)可以是任何类型的数据,包括其他对象。

对象可以被看作是属性的容器,每个属性都有一个名称和一个值。属性名称通常是字符串,也可以是Symbol(ES6引入)。

对象与原始类型的区别

JavaScript中的数据类型可以分为两类:

  1. 原始类型(Primitive Types):字符串、数值、布尔值、null、undefined、Symbol和BigInt
  2. 引用类型(Reference Types):对象(包括数组、函数、日期等特殊对象)

原始类型和对象之间的主要区别:

  • 原始类型按值传递,对象按引用传递
  • 原始类型是不可变的,对象是可变的
  • 原始类型直接存储在栈内存中,对象存储在堆内存中
javascript
// 原始类型按值传递
let a = 10;
let b = a;
a = 20;
console.log(b); // 10(b不受a的变化影响)

// 对象按引用传递
let obj1 = { name: "张三" };
let obj2 = obj1;
obj1.name = "李四";
console.log(obj2.name); // "李四"(obj2受obj1的变化影响)

创建对象

JavaScript提供了多种创建对象的方式:

对象字面量

对象字面量是创建对象最简单、最常用的方式,使用花括号{}包裹键值对:

javascript
// 空对象
const emptyObject = {};

// 包含属性的对象
const person = {
  name: "张三",
  age: 30,
  isStudent: false,
  address: {
    city: "北京",
    district: "朝阳区"
  },
  hobbies: ["读书", "旅游", "摄影"]
};

构造函数

使用Object构造函数可以创建一个空对象,然后添加属性:

javascript
const person = new Object();
person.name = "张三";
person.age = 30;
person.isStudent = false;

Object.create()

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

javascript
const personProto = {
  greeting: function() {
    return `你好,我是${this.name}`;
  }
};

const person = Object.create(personProto);
person.name = "张三";
person.age = 30;

console.log(person.greeting()); // "你好,我是张三"

构造函数模式

可以定义自己的构造函数来创建特定类型的对象:

javascript
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.greeting = function() {
    return `你好,我是${this.name}`;
  };
}

const person1 = new Person("张三", 30);
const person2 = new Person("李四", 25);

console.log(person1.greeting()); // "你好,我是张三"
console.log(person2.greeting()); // "你好,我是李四"

ES6 类语法

ES6引入了类(class)语法,提供了更清晰的面向对象编程方式:

javascript
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  greeting() {
    return `你好,我是${this.name}`;
  }
}

const person = new Person("张三", 30);
console.log(person.greeting()); // "你好,我是张三"

访问和操作属性

点表示法

点表示法是访问对象属性最常用的方式:

javascript
const person = {
  name: "张三",
  age: 30
};

console.log(person.name); // "张三"
person.age = 31;
console.log(person.age); // 31

方括号表示法

方括号表示法允许使用变量或包含特殊字符的属性名:

javascript
const person = {
  name: "张三",
  age: 30,
  "full-name": "张三丰" // 包含特殊字符的属性名
};

const key = "name";
console.log(person[key]); // "张三"
console.log(person["age"]); // 30
console.log(person["full-name"]); // "张三丰"

添加属性

可以随时向对象添加新属性:

javascript
const person = {
  name: "张三"
};

person.age = 30;
person["isStudent"] = false;
person.address = {
  city: "北京",
  district: "朝阳区"
};

console.log(person);
// {
//   name: "张三",
//   age: 30,
//   isStudent: false,
//   address: { city: "北京", district: "朝阳区" }
// }

删除属性

使用delete运算符可以删除对象的属性:

javascript
const person = {
  name: "张三",
  age: 30,
  isStudent: false
};

delete person.isStudent;
console.log(person); // { name: "张三", age: 30 }

// 删除不存在的属性不会报错
delete person.job; // 不会报错

检查属性是否存在

有多种方法可以检查对象是否包含某个属性:

javascript
const person = {
  name: "张三",
  age: 30,
  address: null
};

// 使用in运算符(检查自有属性和继承属性)
console.log("name" in person); // true
console.log("toString" in person); // true(继承自Object.prototype)

// 使用hasOwnProperty()方法(只检查自有属性)
console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("toString")); // false

// 使用undefined比较(不推荐,因为属性值可能就是undefined)
console.log(person.name !== undefined); // true
console.log(person.job !== undefined); // false

// 检查属性是否为null或undefined
console.log(person.address != null); // false(因为address是null)

属性枚举

可以使用多种方法枚举对象的属性:

javascript
const person = {
  name: "张三",
  age: 30,
  isStudent: false
};

// for...in循环(遍历自有属性和继承的可枚举属性)
for (let key in person) {
  console.log(key + ": " + person[key]);
}
// 输出:
// name: 张三
// age: 30
// isStudent: false

// Object.keys()(返回自有可枚举属性名称的数组)
const keys = Object.keys(person);
console.log(keys); // ["name", "age", "isStudent"]

// Object.values()(返回自有可枚举属性值的数组)
const values = Object.values(person);
console.log(values); // ["张三", 30, false]

// Object.entries()(返回自有可枚举属性的[key, value]对数组)
const entries = Object.entries(person);
console.log(entries);
// [["name", "张三"], ["age", 30], ["isStudent", false]]

对象的高级特性

属性描述符

ES5引入了属性描述符,允许精确定义属性的特性:

javascript
const person = {};

// 定义一个普通的数据属性
Object.defineProperty(person, "name", {
  value: "张三",
  writable: true,     // 是否可写
  enumerable: true,   // 是否可枚举
  configurable: true  // 是否可配置(删除、修改特性)
});

// 定义一个访问器属性(getter和setter)
let age = 30;
Object.defineProperty(person, "age", {
  get: function() {
    return age;
  },
  set: function(newValue) {
    if (newValue > 0) {
      age = newValue;
    }
  },
  enumerable: true,
  configurable: true
});

person.age = 31;
console.log(person.age); // 31
person.age = -5; // 无效,因为setter验证了值
console.log(person.age); // 31

// 一次定义多个属性
Object.defineProperties(person, {
  job: {
    value: "工程师",
    writable: true,
    enumerable: true,
    configurable: true
  },
  salary: {
    value: 10000,
    writable: false, // 只读属性
    enumerable: false, // 不可枚举
    configurable: false // 不可配置
  }
});

// 获取属性描述符
console.log(Object.getOwnPropertyDescriptor(person, "name"));
// {value: "张三", writable: true, enumerable: true, configurable: true}

// 获取所有属性的描述符
console.log(Object.getOwnPropertyDescriptors(person));

对象的可扩展性

JavaScript提供了几种控制对象可扩展性的方法:

javascript
const person = {
  name: "张三"
};

// 防止添加新属性
Object.preventExtensions(person);
person.age = 30; // 在严格模式下会抛出错误,非严格模式下静默失败
console.log(person.age); // undefined
console.log(Object.isExtensible(person)); // false

// 封闭对象(防止添加新属性和配置现有属性,但可以修改现有属性的值)
const person2 = { name: "李四" };
Object.seal(person2);
person2.name = "李四四"; // 可以修改
delete person2.name; // 无法删除
person2.age = 25; // 无法添加
console.log(person2); // { name: "李四四" }
console.log(Object.isSealed(person2)); // true

// 冻结对象(最严格的限制,对象变为不可变)
const person3 = { name: "王五" };
Object.freeze(person3);
person3.name = "王五五"; // 无法修改
delete person3.name; // 无法删除
person3.age = 22; // 无法添加
console.log(person3); // { name: "王五" }
console.log(Object.isFrozen(person3)); // true

计算属性名

ES6允许在对象字面量中使用表达式作为属性名:

javascript
const prefix = "user";
const id = 1;

const user = {
  [`${prefix}_${id}`]: "张三",
  [`${prefix}Age`]: 30
};

console.log(user); // { user_1: "张三", userAge: 30 }

对象方法的简写

ES6提供了对象方法的简写语法:

javascript
// ES5
const person1 = {
  name: "张三",
  sayHello: function() {
    return "你好," + this.name;
  }
};

// ES6简写
const person2 = {
  name: "张三",
  sayHello() {
    return `你好,${this.name}`;
  }
};

console.log(person2.sayHello()); // "你好,张三"

对象属性的简写

ES6允许在对象字面量中直接使用变量作为属性名和值:

javascript
const name = "张三";
const age = 30;

// ES5
const person1 = {
  name: name,
  age: age
};

// ES6简写
const person2 = {
  name,
  age
};

console.log(person2); // { name: "张三", age: 30 }

对象的复制与合并

浅拷贝

浅拷贝只复制对象的第一层属性,嵌套对象仍然共享引用:

javascript
// 使用Object.assign()
const original = {
  name: "张三",
  age: 30,
  address: {
    city: "北京"
  }
};

const copy1 = Object.assign({}, original);
original.name = "李四";
original.address.city = "上海";

console.log(copy1.name); // "张三"(基本类型复制了值)
console.log(copy1.address.city); // "上海"(嵌套对象共享引用)

// 使用展开运算符
const copy2 = { ...original };
console.log(copy2); // 与copy1结果相同

深拷贝

深拷贝会递归复制所有层级的属性,创建完全独立的副本:

javascript
// 使用JSON(有限制:不能处理函数、Symbol、undefined等)
const original = {
  name: "张三",
  age: 30,
  address: {
    city: "北京"
  }
};

const deepCopy = JSON.parse(JSON.stringify(original));
original.address.city = "上海";
console.log(deepCopy.address.city); // "北京"(完全独立)

// 使用递归函数实现深拷贝
function deepClone(obj) {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }
  
  // 处理日期
  if (obj instanceof Date) {
    return new Date(obj.getTime());
  }
  
  // 处理数组
  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item));
  }
  
  // 处理对象
  const cloned = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloned[key] = deepClone(obj[key]);
    }
  }
  
  return cloned;
}

const original2 = {
  name: "张三",
  birthdate: new Date(1990, 0, 1),
  hobbies: ["读书", "旅游"],
  address: {
    city: "北京",
    district: "朝阳区"
  }
};

const deepCopy2 = deepClone(original2);
original2.address.city = "上海";
original2.hobbies.push("摄影");

console.log(deepCopy2.address.city); // "北京"
console.log(deepCopy2.hobbies); // ["读书", "旅游"]

对象合并

合并多个对象的属性:

javascript
const defaults = {
  theme: "light",
  fontSize: 14,
  showSidebar: true
};

const userSettings = {
  theme: "dark",
  showNotifications: true
};

// 使用Object.assign()合并(后面的对象属性会覆盖前面的)
const settings1 = Object.assign({}, defaults, userSettings);
console.log(settings1);
// {
//   theme: "dark",
//   fontSize: 14,
//   showSidebar: true,
//   showNotifications: true
// }

// 使用展开运算符合并
const settings2 = { ...defaults, ...userSettings };
console.log(settings2); // 结果同上

对象解构

ES6引入了对象解构语法,使提取对象属性变得更加简洁:

javascript
const person = {
  name: "张三",
  age: 30,
  address: {
    city: "北京",
    district: "朝阳区"
  },
  hobbies: ["读书", "旅游", "摄影"]
};

// 基本解构
const { name, age } = person;
console.log(name, age); // "张三" 30

// 解构并重命名
const { name: fullName, age: years } = person;
console.log(fullName, years); // "张三" 30

// 设置默认值
const { job = "工程师" } = person;
console.log(job); // "工程师"(因为person对象中没有job属性)

// 嵌套解构
const { address: { city, district } } = person;
console.log(city, district); // "北京" "朝阳区"

// 解构数组属性
const { hobbies: [firstHobby, secondHobby] } = person;
console.log(firstHobby, secondHobby); // "读书" "旅游"

// 剩余参数
const { name: personName, ...rest } = person;
console.log(personName); // "张三"
console.log(rest); // { age: 30, address: {...}, hobbies: [...] }

// 函数参数解构
function printPersonInfo({ name, age, address: { city } = {} } = {}) {
  console.log(`${name}, ${age}岁, 来自${city}`);
}

printPersonInfo(person); // "张三, 30岁, 来自北京"
printPersonInfo({}); // "undefined, undefined岁, 来自undefined"
printPersonInfo(); // "undefined, undefined岁, 来自undefined"

对象与JSON

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,与JavaScript对象字面量语法相似:

javascript
// 对象转JSON字符串
const person = {
  name: "张三",
  age: 30,
  isStudent: false,
  address: {
    city: "北京"
  },
  hobbies: ["读书", "旅游"]
};

const jsonString = JSON.stringify(person);
console.log(jsonString);
// {"name":"张三","age":30,"isStudent":false,"address":{"city":"北京"},"hobbies":["读书","旅游"]}

// 格式化输出
const prettyJson = JSON.stringify(person, null, 2);
console.log(prettyJson);
// {
//   "name": "张三",
//   "age": 30,
//   "isStudent": false,
//   "address": {
//     "city": "北京"
//   },
//   "hobbies": [
//     "读书",
//     "旅游"
//   ]
// }

// 选择性转换
const partialJson = JSON.stringify(person, ["name", "age"]);
console.log(partialJson); // {"name":"张三","age":30}

// 使用替换函数
const customJson = JSON.stringify(person, (key, value) => {
  if (key === "age") {
    return value + 1;
  }
  return value;
});
console.log(customJson); // 年龄变成了31

// JSON字符串转对象
const jsonStr = '{"name":"李四","age":25,"hobbies":["游泳","跑步"]}';
const obj = JSON.parse(jsonStr);
console.log(obj.name); // "李四"
console.log(obj.hobbies[0]); // "游泳"

// 使用转换函数
const dateJson = '{"name":"王五","birthdate":"1995-05-15"}';
const objWithDate = JSON.parse(dateJson, (key, value) => {
  if (key === "birthdate") {
    return new Date(value);
  }
  return value;
});
console.log(objWithDate.birthdate instanceof Date); // true
console.log(objWithDate.birthdate.getFullYear()); // 1995

对象方法

对象可以包含方法(函数作为属性值):

javascript
const calculator = {
  value: 0,
  
  add(x) {
    this.value += x;
    return this; // 返回this以支持链式调用
  },
  
  subtract(x) {
    this.value -= x;
    return this;
  },
  
  multiply(x) {
    this.value *= x;
    return this;
  },
  
  divide(x) {
    if (x !== 0) {
      this.value /= x;
    }
    return this;
  },
  
  getValue() {
    return this.value;
  },
  
  reset() {
    this.value = 0;
    return this;
  }
};

// 链式调用
const result = calculator
  .add(5)
  .multiply(2)
  .subtract(3)
  .divide(2)
  .getValue();

console.log(result); // 3.5

this关键字

在对象方法中,this关键字指向调用该方法的对象:

javascript
const person = {
  name: "张三",
  
  sayHello() {
    return `你好,我是${this.name}`;
  },
  
  changeName(newName) {
    this.name = newName;
  }
};

console.log(person.sayHello()); // "你好,我是张三"
person.changeName("张三丰");
console.log(person.sayHello()); // "你好,我是张三丰"

// this的值取决于函数如何被调用
const greet = person.sayHello;
console.log(greet()); // "你好,我是undefined"(因为此时this不再指向person)

// 使用bind固定this
const boundGreet = person.sayHello.bind(person);
console.log(boundGreet()); // "你好,我是张三丰"

实用对象技巧

对象作为映射

对象可以用作简单的键值映射(字典):

javascript
// 使用对象作为映射
const userRoles = {
  "user123": "admin",
  "user456": "editor",
  "user789": "viewer"
};

console.log(userRoles["user456"]); // "editor"

// 检查键是否存在
const userId = "user123";
if (userRoles[userId]) {
  console.log(`用户角色: ${userRoles[userId]}`);
}

// 注意:使用对象作为映射有一些限制
// 1. 键总是被转换为字符串
// 2. 对象有内置的属性(如toString)可能导致冲突

// 更好的选择是使用Map(ES6)
const roleMap = new Map();
roleMap.set("user123", "admin");
roleMap.set("user456", "editor");
roleMap.set("user789", "viewer");

console.log(roleMap.get("user456")); // "editor"
console.log(roleMap.has("user999")); // false

对象作为函数参数

使用对象作为函数参数可以提供更灵活的API:

javascript
// 传统方式:参数顺序固定
function createUser(name, age, isAdmin, email, address) {
  // ...
}

// 对象参数:顺序不重要,可选参数更清晰
function createUser({ name, age, isAdmin = false, email, address = {} }) {
  console.log(`创建用户: ${name}, ${age}岁, 管理员: ${isAdmin}`);
  console.log(`邮箱: ${email}, 地址: ${address.city || "未知"}`);
}

createUser({
  name: "张三",
  age: 30,
  email: "zhangsan@example.com",
  isAdmin: true,
  address: { city: "北京" }
});

// 也可以部分省略参数
createUser({
  name: "李四",
  age: 25,
  email: "lisi@example.com"
});

对象的动态访问

可以动态地访问和修改对象属性:

javascript
const user = {
  name: "张三",
  age: 30,
  email: "zhangsan@example.com"
};

// 动态获取属性
function getProperty(obj, propName) {
  return obj[propName];
}

console.log(getProperty(user, "name")); // "张三"

// 动态设置属性
function setProperty(obj, propName, value) {
  obj[propName] = value;
}

setProperty(user, "age", 31);
console.log(user.age); // 31

// 动态创建属性
const propName = "occupation";
user[propName] = "工程师";
console.log(user.occupation); // "工程师"

对象字面量增强

ES6提供了多种对象字面量增强功能:

javascript
// 属性简写
const name = "张三";
const age = 30;

const user = {
  name,  // 等同于 name: name
  age,   // 等同于 age: age
  
  // 方法简写
  sayHello() {
    return `你好,我是${this.name}`;
  },
  
  // 计算属性名
  [`user_${Date.now()}`]: "唯一ID",
  
  // getter和setter
  get info() {
    return `${this.name}, ${this.age}岁`;
  },
  
  set info(value) {
    [this.name, this.age] = value.split(",");
    this.age = parseInt(this.age);
  }
};

console.log(user.sayHello()); // "你好,我是张三"
console.log(user.info); // "张三, 30岁"
user.info = "李四,25";
console.log(user.name, user.age); // "李四" 25

总结

JavaScript对象是一种强大而灵活的数据结构,是JavaScript编程的核心。本文介绍了对象的基础概念、创建方法、属性操作和常用技巧。

关键要点:

  1. 对象是键值对的集合,键是字符串或Symbol,值可以是任何类型
  2. 创建对象的方式包括对象字面量、构造函数、Object.create()和类语法
  3. 可以使用点表示法或方括号表示法访问对象属性
  4. ES6引入了许多对象增强功能,如简写语法、计算属性名和解构赋值
  5. 对象的复制分为浅拷贝和深拷贝,处理嵌套对象时需要特别注意
  6. 对象可以用作函数参数,提供更灵活的API设计
  7. JSON是基于JavaScript对象语法的数据交换格式

通过深入理解对象,你可以更有效地组织和操作数据,编写更清晰、更灵活的JavaScript代码。

用知识点燃技术的火山