什么是 Object?
JavaScript 中的对象是一个键值对(Key-Value)的集合。
- 键(Key):通常是字符串(Symbol 也可以作为键)。
- 值(Value):可以是任何数据类型,包括数字、字符串、布尔值、函数(方法)、数组,甚至是另一个对象。
可以把对象想象成一个“袋子”,里面装着各种带有标签(键)的物品(值)。
const person = {
name: "Alice", // 字符串
age: 25, // 数字
isStudent: false, // 布尔值
sayHi: function() { // 函数(方法)
console.log("Hello!");
},
hobbies: ["coding", "reading"] // 数组
};
创建对象的多种方式
对象字面量(最常用)
这是最简单、最推荐的方式。
const obj = {
key: "value"
};
使用 new Object() 构造函数
虽然可行,但代码冗长,不推荐日常使用。
const obj = new Object();
obj.name = "Bob";
使用 Object.create()
用于基于原型创建对象,常用于高级继承模式。
const proto = { greet: () => console.log("Hi") };
const obj = Object.create(proto);
使用类(Class)(ES6+)
类本质上是对象的模板。
class Person {
constructor(name) {
this.name = name;
}
}
const p = new Person("Charlie");
属性
数据属性(Data Property)
数据属性是 JavaScript 对象中的一种属性,它直接保存 一个值,而不是通过 getter 和 setter 来访问或修改该值。
简而言之,数据属性就是一个简单的属性,你给它一个值,它就存储这个值。
const obj = {
name: "Alice",
age: 30
};
在这个例子中,name 和 age 就是数据属性。它们存储了 "Alice" 和 30 这两个值。
数据属性的描述符(Property Descriptor)
每个数据属性都有 四个特性(特征),这些特性决定了数据属性的行为:
| 特性 | 说明 |
|---|---|
value |
属性的值,存储的数据。可以是任何类型,如数字、字符串、对象、数组等。 |
writable |
是否可以修改该属性的值。true 表示可以修改,false 表示不可修改(只读)。 |
enumerable |
是否可以通过 for...in、Object.keys() 等方法遍历该属性。true 表示可以遍历,false 表示不可遍历。 |
configurable |
是否可以删除该属性或修改该属性的描述符(如 writable、enumerable 等)。true 表示可以删除或修改,false 表示不可删除或修改。 |
每个数据属性实际上是通过属性描述符来定义的,属性描述符(Property Descriptor) 是一个包含上述四个特性的对象。
const obj = {
name: "Alice"
};
const descriptor = Object.getOwnPropertyDescriptor(obj, "name");
console.log(descriptor);
/*
输出:
{
value: "Alice",
writable: true,
enumerable: true,
configurable: true
}
*/
使用 Object.defineProperty() 定义数据属性
使用 Object.defineProperty() 还可以修改已存在的属性描述符。
const person = {};
Object.defineProperty(person, "name", {
value: "Alice",
writable: true,
enumerable: true,
configurable: true
});
console.log(person.name); // 输出: Alice
person.name = "Bob"; // 修改成功
console.log(person.name); // 输出: Bob
Object.defineProperty(person, "name", {
writable: false
});
person.name = "Charlie"; // 修改失败
console.log(person.name); // 输出: Bob
delete person.name; // 删除成功
console.log(person.name); // 输出: undefined
方法其实也是数据属性
JS 里“函数属性”和“方法”其实本质是同一件事
const obj = {
sayHello: function () {
console.log("hello")
}
}
// 两种写法其实是一样的
const obj = {
sayHello() {
console.log("hello")
}
}
// 但本质仍然是:sayHello : function (函数属性)
方法/函数属性其是数据属性
const obj = {
sayHello(){
console.log("hello")
}
}
// 其 描述符 为
{
value: function sayHello(){}, // value = function
writable: true,
enumerable: true,
configurable: true
}
函数属性和方法区别主要是语义和调用方式不同
const obj = {
fn: function () {} // 函数属性只是存储函数:
}
console.log(obj.fn) // 输出 function
obj.fn() // 而方法是 调用函数
最重要的区别其实是 调用方式影响 this。
const obj = {
name: "Tom",
say() {
console.log(this.name)
}
}
obj.say() // 输出:Tom 。 此时:this = obj
const fn = obj.say
fn() // 输出:undefined。this = global / undefined
属性的访问和修改
读取数据属性的内部流程
const obj = {
x: 10
};
console.log(obj.x); // 访问数据属性 x
---------------------------------------
-------------------内部流程-------------
obj.x
↓
[[Get]](obj, "x") // 调用内部的[[Get]]方法,查找属性 x
↓
找到数据属性 x,直接返回属性值 10
修改数据属性的内部流程
const obj = {
x: 10
};
obj.x = 20; // 修改数据属性 x 的值
console.log(obj.x); // 输出修改后的值
-------------------------------------------
------------------内部流程------------------
obj.x = 20
↓
[[Set]](obj, "x", 20) // 调用内部的[[Set]]方法,设置属性 x 为 20
↓
修改属性 x 的 value 为 20
访问器属性(Accessor Property)
访问器属性不直接存储值,而是通过函数(getter 和 setter)来计算属性的值。当我们访问或者修改该属性时,实际上是在调用相应的 getter 或 setter 函数。
访问器属性由 get 和 set 两个函数组成:
get:定义了读取属性值时的行为。set:定义了修改属性值时的行为。
const obj = {
_x: 10,
get x() {
console.log("Getting x");
return this._x; // 获取 _x 的值
},
set x(value) {
console.log("Setting x to", value);
this._x = value; // 设置 _x 的值
}
};
在这个例子中,x 是一个 访问器属性,它通过 get 和 set 来控制访问和修改 _x 属性的值。
访问器属性的描述符
| 特性 | 说明 |
|---|---|
get |
一个函数,用于读取属性值。 |
set |
一个函数,用于设置属性值。 |
enumerable |
是否可以通过 for...in 或 Object.keys() 遍历到该属性。 |
configurable |
是否可以删除该属性或修改其描述符。 |
const obj = {
_x: 10,
get x() {
return this._x;
},
set x(value) {
this._x = value;
}
};
Object.getOwnPropertyDescriptor(obj, 'x'); // 显示描述符
/*
输出:
{
get: function() { return this._x; },
set: function(value) { this._x = value; },
enumerable: true,
configurable: true
}
*/
使用 Object.defineProperty() 定义访问器属性
Object.defineProperty() 让你可以精确控制属性的描述符,并允许你定义访问器属性。
const obj = {};
Object.defineProperty(obj, "x", {
get() {
return this._x;
},
set(value) {
this._x = value;
},
enumerable: true,
configurable: true
});
访问器属性的应用场景
数据验证
const obj = {
_age: 30,
get age() {
return this._age;
},
set age(value) {
if (value < 0) {
console.log("Invalid age");
return;
}
this._age = value;
}
};
obj.age = -5; // 输出: Invalid age
console.log(obj.age); // 输出: 30
数据派生
可以使用 get 方法来计算派生数据。比如,计算一个值并将其返回,而不需要直接存储它。
const obj = {
_radius: 5,
get area() {
return Math.PI * this._radius * this._radius;
}
};
console.log(obj.area); // 输出: 78.53981633974483
area 不是直接存储的属性值,而是通过计算 radius 得到的派生值。
封装复杂逻辑
可以使用 get 和 set 来封装复杂的属性访问逻辑,使得外部代码不需要知道属性是如何存储和计算的。
const obj = {
_temperatureInCelsius: 25,
get temperature() {
return this._temperatureInCelsius * 9 / 5 + 32; // 转换为华氏温度
},
set temperature(value) {
this._temperatureInCelsius = (value - 32) * 5 / 9; // 转换为摄氏温度
}
};
console.log(obj.temperature); // 输出: 77 (华氏温度)
obj.temperature = 100; // 设置为摄氏温度 100
console.log(obj._temperatureInCelsius); // 输出: 37.77777777777778 (摄氏温度)
属性的访问和修改
读取访问器属性的内部流程
const obj = {
_x: 10,
get x() {
return this._x;
}
};
console.log(obj.x); // 访问访问器属性 x
内部流程:
obj.x
↓
[[Get]](obj, "x") // 调用内部的[[Get]]方法,查找属性 x
↓
找到访问器属性 x,发现是访问属性就会调用 getter 方法
↓
执行 getter() { return this._x; },返回 this._x 的值,即 10
this._x:会走数据属性的流程
修改访问器属性的内部流程
const obj = {
_x: 10,
set x(value) {
this._x = value;
}
};
obj.x = 20; // 修改访问器属性 x 的值
console.log(obj._x); // 输出修改后的值
内部流程:
obj.x = 20
↓
[[Set]](obj, "x", 20) // 调用内部的[[Set]]方法,设置属性 x 为 20
↓
找到访问器属性 x,发现是访问器属性,调用 setter 方法
↓
执行 setter(value) { this._x = value; },将 this._x 设置为 20
this._x = value:会走数据属性的流程
常用操作方法
JavaScript 内置了强大的 Object 全局对象,提供了许多实用方法:
| 方法 | 描述 | 示例 |
|---|---|---|
Object.keys(obj) |
获取所有可枚举的键名数组 | ['name', 'age'] |
Object.values(obj) |
获取所有可枚举的值数组 | ['Alice', 25] |
Object.entries(obj) |
获取 [键, 值] 对的二维数组 |
[['name', 'Alice'], ['age', 25]] |
Object.assign(target, ...sources) |
合并对象(浅拷贝) | Object.assign({}, obj1, obj2) |
Object.freeze(obj) |
冻结对象,不可修改 | 防止意外篡改配置 |
Object.hasOwnProperty(key) |
判断属性是否属于对象自身(非原型链) | obj.hasOwnProperty('name') |
遍历对象的几种方式:
const user = { name: "Tom", age: 30 };
// 1. for...in (会遍历原型链上的可枚举属性,需配合 hasOwnProperty 使用)
for (let key in user) {
if (user.hasOwnProperty(key)) {
console.log(key, user[key]);
}
}
// 2. Object.keys + forEach (推荐)
Object.keys(user).forEach(key => {
console.log(key, user[key]);
});
// 3. Object.entries + for...of (最现代,适合解构)
for (const [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`);
}