JS:Object对象

什么是 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 对象中的一种属性,它直接保存 一个值,而不是通过 gettersetter 来访问或修改该值。

简而言之,数据属性就是一个简单的属性,你给它一个值,它就存储这个值。

const obj = {
  name: "Alice",
  age: 30
};

在这个例子中,nameage 就是数据属性。它们存储了 "Alice"30 这两个值。

数据属性的描述符(Property Descriptor)

每个数据属性都有 四个特性(特征),这些特性决定了数据属性的行为:

特性 说明
value 属性的值,存储的数据。可以是任何类型,如数字、字符串、对象、数组等。
writable 是否可以修改该属性的值。true 表示可以修改,false 表示不可修改(只读)。
enumerable 是否可以通过 for...inObject.keys() 等方法遍历该属性。true 表示可以遍历,false 表示不可遍历。
configurable 是否可以删除该属性或修改该属性的描述符(如 writableenumerable 等)。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)

访问器属性不直接存储值,而是通过函数(gettersetter)来计算属性的值。当我们访问或者修改该属性时,实际上是在调用相应的 getter 或 setter 函数。

访问器属性由 getset 两个函数组成:

  • 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 是一个 访问器属性,它通过 getset 来控制访问和修改 _x 属性的值。

访问器属性的描述符

特性 说明
get 一个函数,用于读取属性值。
set 一个函数,用于设置属性值。
enumerable 是否可以通过 for...inObject.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 得到的派生值。

封装复杂逻辑

可以使用 getset 来封装复杂的属性访问逻辑,使得外部代码不需要知道属性是如何存储和计算的。

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}`);
}

×

喜欢就点赞,疼爱就打赏