JavaScript对象基础
对象是JavaScript中最重要的数据类型之一,几乎所有JavaScript中的值最终都是对象。对象是一种复合值,它可以将多个相关的值(属性)聚合在一起,形成一个整体。本文将详细介绍JavaScript中对象的基础概念、创建方法、属性操作和常用技巧。
对象概述
什么是对象
在JavaScript中,对象是一种复合数据类型,用来存储各种键值对(key-value pairs)。每个键值对被称为属性(property),其中键(key)是一个字符串(或Symbol),值(value)可以是任何类型的数据,包括其他对象。
对象可以被看作是属性的容器,每个属性都有一个名称和一个值。属性名称通常是字符串,也可以是Symbol(ES6引入)。
对象与原始类型的区别
JavaScript中的数据类型可以分为两类:
- 原始类型(Primitive Types):字符串、数值、布尔值、null、undefined、Symbol和BigInt
- 引用类型(Reference Types):对象(包括数组、函数、日期等特殊对象)
原始类型和对象之间的主要区别:
- 原始类型按值传递,对象按引用传递
- 原始类型是不可变的,对象是可变的
- 原始类型直接存储在栈内存中,对象存储在堆内存中
// 原始类型按值传递
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提供了多种创建对象的方式:
对象字面量
对象字面量是创建对象最简单、最常用的方式,使用花括号{}
包裹键值对:
// 空对象
const emptyObject = {};
// 包含属性的对象
const person = {
name: "张三",
age: 30,
isStudent: false,
address: {
city: "北京",
district: "朝阳区"
},
hobbies: ["读书", "旅游", "摄影"]
};
构造函数
使用Object
构造函数可以创建一个空对象,然后添加属性:
const person = new Object();
person.name = "张三";
person.age = 30;
person.isStudent = false;
Object.create()
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
:
const personProto = {
greeting: function() {
return `你好,我是${this.name}`;
}
};
const person = Object.create(personProto);
person.name = "张三";
person.age = 30;
console.log(person.greeting()); // "你好,我是张三"
构造函数模式
可以定义自己的构造函数来创建特定类型的对象:
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)语法,提供了更清晰的面向对象编程方式:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greeting() {
return `你好,我是${this.name}`;
}
}
const person = new Person("张三", 30);
console.log(person.greeting()); // "你好,我是张三"
访问和操作属性
点表示法
点表示法是访问对象属性最常用的方式:
const person = {
name: "张三",
age: 30
};
console.log(person.name); // "张三"
person.age = 31;
console.log(person.age); // 31
方括号表示法
方括号表示法允许使用变量或包含特殊字符的属性名:
const person = {
name: "张三",
age: 30,
"full-name": "张三丰" // 包含特殊字符的属性名
};
const key = "name";
console.log(person[key]); // "张三"
console.log(person["age"]); // 30
console.log(person["full-name"]); // "张三丰"
添加属性
可以随时向对象添加新属性:
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
运算符可以删除对象的属性:
const person = {
name: "张三",
age: 30,
isStudent: false
};
delete person.isStudent;
console.log(person); // { name: "张三", age: 30 }
// 删除不存在的属性不会报错
delete person.job; // 不会报错
检查属性是否存在
有多种方法可以检查对象是否包含某个属性:
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)
属性枚举
可以使用多种方法枚举对象的属性:
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引入了属性描述符,允许精确定义属性的特性:
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提供了几种控制对象可扩展性的方法:
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允许在对象字面量中使用表达式作为属性名:
const prefix = "user";
const id = 1;
const user = {
[`${prefix}_${id}`]: "张三",
[`${prefix}Age`]: 30
};
console.log(user); // { user_1: "张三", userAge: 30 }
对象方法的简写
ES6提供了对象方法的简写语法:
// ES5
const person1 = {
name: "张三",
sayHello: function() {
return "你好," + this.name;
}
};
// ES6简写
const person2 = {
name: "张三",
sayHello() {
return `你好,${this.name}`;
}
};
console.log(person2.sayHello()); // "你好,张三"
对象属性的简写
ES6允许在对象字面量中直接使用变量作为属性名和值:
const name = "张三";
const age = 30;
// ES5
const person1 = {
name: name,
age: age
};
// ES6简写
const person2 = {
name,
age
};
console.log(person2); // { name: "张三", age: 30 }
对象的复制与合并
浅拷贝
浅拷贝只复制对象的第一层属性,嵌套对象仍然共享引用:
// 使用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结果相同
深拷贝
深拷贝会递归复制所有层级的属性,创建完全独立的副本:
// 使用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); // ["读书", "旅游"]
对象合并
合并多个对象的属性:
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引入了对象解构语法,使提取对象属性变得更加简洁:
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对象字面量语法相似:
// 对象转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
对象方法
对象可以包含方法(函数作为属性值):
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
关键字指向调用该方法的对象:
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()); // "你好,我是张三丰"
实用对象技巧
对象作为映射
对象可以用作简单的键值映射(字典):
// 使用对象作为映射
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:
// 传统方式:参数顺序固定
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"
});
对象的动态访问
可以动态地访问和修改对象属性:
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提供了多种对象字面量增强功能:
// 属性简写
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编程的核心。本文介绍了对象的基础概念、创建方法、属性操作和常用技巧。
关键要点:
- 对象是键值对的集合,键是字符串或Symbol,值可以是任何类型
- 创建对象的方式包括对象字面量、构造函数、Object.create()和类语法
- 可以使用点表示法或方括号表示法访问对象属性
- ES6引入了许多对象增强功能,如简写语法、计算属性名和解构赋值
- 对象的复制分为浅拷贝和深拷贝,处理嵌套对象时需要特别注意
- 对象可以用作函数参数,提供更灵活的API设计
- JSON是基于JavaScript对象语法的数据交换格式
通过深入理解对象,你可以更有效地组织和操作数据,编写更清晰、更灵活的JavaScript代码。