JavaScript中的`Object`类

JavaScript中的`Object`类

本文介绍了JavaScript中的Object类。

本文讲解了JavaScript中的Object类,并包含实际示例。

YouTube Video

JavaScript中的Object

Object是一个内置对象,是所有JavaScript对象的基础。该语言的许多核心功能,如属性管理、继承(原型链)、枚举、克隆和冻结,都是通过Object的行为实现的。

Object.create

创建对象有多种方式,应根据目的合理选择。

对象字面量(最常用)

下面的代码展示了创建对象最简单且最易读的方法。

 1// Create an object using object literal
 2const user = {
 3  name: "Alice",
 4  age: 30,
 5  greet() {
 6    return `Hello, I'm ${this.name}`;
 7  }
 8};
 9
10console.log(user.greet()); // "Hello, I'm Alice"
  • 在这个例子中,属性和方法是使用字面量定义的。它很简单,通常表现出优越的性能。

new Object() 构造函数

Object构造函数很少使用,但了解它的行为很有帮助。

1// Create an object using the Object constructor
2const objFromCtor = new Object();
3objFromCtor.x = 10;
4objFromCtor.y = 20;
5
6console.log(objFromCtor); // { x: 10, y: 20 }
  • new Object()返回一个空对象,但字面量{}更简洁且更常用。

使用Object.create指定原型

Object.create用于创建具有指定原型的对象。

1// Create an object with a specified prototype
2const proto = { hello() { return "hi"; } };
3const obj = Object.create(proto);
4obj.name = "Bob";
5
6console.log(obj.hello()); // "hi"
7console.log(Object.getPrototypeOf(obj) === proto); // true
  • Object.create非常适合基于继承的对象设计,可以精确控制原型链。

属性特性和描述符

属性具有'value'、'writable'、'enumerable'和'configurable'等特性,可以通过Object.defineProperty进行详细控制。

defineProperty的基本用法示例

接下来是一个使用 defineProperty 定义不可枚举且只读属性的示例。

 1// Define a non-enumerable read-only property
 2const person = { name: "Carol" };
 3
 4Object.defineProperty(person, "id", {
 5  value: 12345,
 6  writable: false,
 7  enumerable: false,
 8  configurable: false
 9});
10
11console.log(person.id); // 12345
12console.log(Object.keys(person)); // ["name"] — "id" is non-enumerable
13person.id = 999; // silently fails or throws in strict mode
14console.log(person.id); // still 12345
  • 使用 defineProperty 可以精确控制属性的行为,如枚举、重写和删除。

访问器属性(getter / setter

通过访问器,你可以在读取和写入属性时注入逻辑。

 1// Use getter and setter to manage internal state
 2const data = {
 3  _value: 1,
 4  get value() {
 5    return this._value;
 6  },
 7  set value(v) {
 8    if (typeof v === "number" && v > 0) {
 9      this._value = v;
10    } else {
11      throw new Error("value must be a positive number");
12    }
13  }
14};
15
16console.log(data.value); // 1
17data.value = 5;
18console.log(data.value); // 5
19// data.value = -1; // would throw
  • 使用gettersetter,可以将属性访问视为外部API,并添加校验或副作用。

原型与继承(prototype / __proto__ / Object.getPrototypeOf

JavaScript中的继承基于原型链,而不是类。对象可以引用其他对象作为它们的原型。

Object.getPrototypeOfObject.setPrototypeOf

下面的例子展示了如何检查和设置原型。

1// Inspect and change prototype
2const base = { speak() { return "base"; } };
3const derived = Object.create(base);
4console.log(Object.getPrototypeOf(derived) === base); // true
5
6const other = { speak() { return "other"; } };
7Object.setPrototypeOf(derived, other);
8console.log(derived.speak()); // "other"
  • Object.getPrototypeOf 用于获取对象的原型。
  • Object.setPrototypeOf可以改变已有对象的原型,但因为会影响性能,应谨慎使用。

重要的内置方法

我们将详细说明从Object.prototype提供的实例方法中选出的最常用和重要的方法,以及Object拥有的静态方法。

hasOwnProperty, isPrototypeOf, toString, valueOf

hasOwnPropertyisPrototypeOftoStringvalueOf 定义了对象的基本行为。

 1// Demonstrate prototype methods
 2const base = { greet() { return "hello"; } };
 3const child = Object.create(base);
 4const a = { x: 1 };
 5
 6console.log(a.hasOwnProperty("x")); // true
 7console.log(a.hasOwnProperty("toString")); // false — toString is inherited
 8
 9console.log(a.toString()); // "[object Object]" by default
10
11console.log(base.isPrototypeOf(child)); // true
12console.log(Object.prototype.isPrototypeOf(child)); // true
  • hasOwnProperty是用于检查属性是否直接属于对象本身的重要方法。
  • isPrototypeOf 检查目标对象是否将自身作为原型。

Object.keys, Object.values, Object.entries

Object.keysObject.valuesObject.entries返回对象自身可枚举属性的列表。这些方法对遍历和转换很有用。

 1// Keys, values and entries
 2const item = { id: 1, name: "Widget", price: 9.99 };
 3
 4// ["id", "name", "price"]
 5console.log(Object.keys(item));
 6
 7// [1, "Widget", 9.99]
 8console.log(Object.values(item));
 9
10// [["id",1], ["name","Widget"], ["price",9.99]]
11console.log(Object.entries(item));
  • 这些常用于对象的遍历和转换。

Object.assign

Object.assign用于浅拷贝和对象合并。注意:原型和访问器属性不会被拷贝。

1// Shallow copy / merge using Object.assign
2const target = { a: 1 };
3const source = { b: 2 };
4const result = Object.assign(target, source);
5
6console.log(result); // { a: 1, b: 2 }
7console.log(target === result); // true (merged into target)
  • 对于嵌套对象,只会拷贝引用,因此深克隆需要采用其他实现方式。

Object.freeze, Object.seal, Object.preventExtensions

Object.freezeObject.sealObject.preventExtensions用于控制对象的可变性。

 1// Freeze vs seal vs preventExtensions
 2const obj = { a: 1 };
 3Object.freeze(obj);
 4obj.a = 2; // fails silently or throws in strict mode
 5delete obj.a; // fails
 6
 7const obj2 = { b: 2 };
 8Object.seal(obj2);
 9obj2.b = 3; // allowed
10// delete obj2.b; // fails
11
12const obj3 = { c: 3 };
13Object.preventExtensions(obj3);
14obj3.d = 4; // fails
  • freeze最为严格,可以防止对象属性的任何更改。
  • seal 防止添加或删除属性,但允许更改已有属性的值。
  • preventExtensions 只阻止添加新属性;已存在的属性仍然可以更改或删除。

对象的可枚举性、顺序及for...in / for...of

for...in会枚举可枚举属性名,包括原型链上的属性,因此通常与hasOwnProperty配合使用。结合使用Object.keys()for...of更加安全,也能使你的意图更加明确。

 1// Safe enumeration
 2const obj = Object.create({ inherited: true });
 3obj.own = 1;
 4
 5for (const key in obj) {
 6  if (obj.hasOwnProperty(key)) {
 7    console.log("own prop:", key);
 8  } else {
 9    console.log("inherited prop:", key);
10  }
11}
12
13for (const key of Object.keys(obj)) {
14  console.log("key via Object.keys:", key);
15}
  • 属性枚举的规则在ECMAScript规范中有规定,有些情况下枚举顺序是有保证的,但有些情况下则不保证。通常情况下,被解释为数字的键按升序排列,其他键则按照插入顺序排列。

克隆与深拷贝

对象的拷贝有两种方式:可以使用Object.assign或扩展语法进行浅拷贝,也可以使用递归方法进行深拷贝。根据实际情况选择合适的方式非常重要。

浅拷贝(扩展语法 / Object.assign

1// Shallow copy with spread operator
2const original = { a: 1, nested: { x: 10 } };
3const shallow = { ...original };
4shallow.nested.x = 99;
5console.log(original.nested.x); // 99 — nested object is shared
  • 浅拷贝会让嵌套对象共享引用,因此原对象的变化可能影响副本。

简单深拷贝(有注意事项)

使用JSON序列化是一种快速深拷贝的方法,但会丢失函数、Date、循环引用和undefined值等。要实现真正的深克隆,需要使用专门的库。

1// Deep clone using JSON methods — limited use-cases only
2const source = { a: 1, d: new Date(), nested: { x: 2 } };
3const cloned = JSON.parse(JSON.stringify(source));
4console.log(cloned); // ok for plain data, but Date becomes string, functions lost
  • 基于JSON的方法便于快速处理简单数据,但在常见情况下,它们的行为可能会出错。

混入与对象组合

通常使用混入(mixin)来组合行为,而不是多重继承。

 1// Simple mixin function
 2const canEat = {
 3  eat() { return `${this.name} eats`; }
 4};
 5const canWalk = {
 6  walk() { return `${this.name} walks`; }
 7};
 8
 9function createPerson(name) {
10  const person = { name };
11  return Object.assign(person, canEat, canWalk);
12}
13
14const p = createPerson("Dana");
15console.log(p.eat()); // "Dana eats"
16console.log(p.walk()); // "Dana walks"
  • 混入非常灵活,但要注意属性名冲突和可测试性。

常见陷阱与最佳实践

以下是一些常见陷阱和最佳实践。

  • 可变性 对象默认是可变的。在具有状态管理的应用程序中,可以考虑使用 Object.freeze 或不可变库来实现不可变数据结构。

  • 原型污染 使用Object.assign或循环直接将外部数据合并到对象时,特殊属性(如__proto__constructor)可能导致意外副作用,带来安全风险。在直接合并前应先过滤用户输入。

  • for...in的陷阱 for...in也会枚举原型属性,应通过hasOwnProperty进行检查。使用Object.keys会更加清晰。

  • 滥用浅拷贝 请考虑是否需要深拷贝,以避免嵌套对象的更改影响原对象。

实用示例:不可变对象更新模式

返回新对象而不直接修改状态的模式广泛用于React等库中。

 1// Immutable update example
 2const state = { todos: [{ id: 1, text: "Buy milk", done: false }] };
 3
 4// Toggle todo done immutably
 5function toggleTodo(state, todoId) {
 6  return {
 7    ...state,
 8    todos: state.todos.map(t => t.id === todoId ? { ...t, done: !t.done } : t)
 9  };
10}
11
12const newState = toggleTodo(state, 1);
13console.log(state.todos[0].done); // false
14console.log(newState.todos[0].done); // true
  • 此代码示例演示了如何在不直接修改原始 state 对象的情况下创建新的 state 对象。toggleTodo 函数会复制 todos 数组,只修改目标元素并返回一个新对象,因此原始的 state 保持不变。
  • 不可变更新可以减少副作用,使状态管理更容易。

实用示例:安全合并(警惕原型污染)

合并外部JSON时,应忽略__proto__以防止原型污染。

 1// Safe merge ignoring __proto__ keys
 2function safeMerge(target, source) {
 3  for (const key of Object.keys(source)) {
 4    if (key === "__proto__" || key === "constructor") continue;
 5    target[key] = source[key];
 6  }
 7  return target;
 8}
 9
10const target = {};
11const source = JSON.parse('{"a":1,"__proto__":{"polluted":true}}');
12safeMerge(target, source);
13console.log(target.polluted); // undefined — safe
14console.log({}.polluted); // undefined — prototype not polluted
  • 在库和框架中,这类防护同样重要。

性能注意事项

关于性能,请注意以下几点:。

  • 避免频繁更改原型(Object.setPrototypeOf)或动态增删属性,这会妨碍引擎优化。
  • 当生成大量小对象时,如果使用结构统一(具有相同属性集合)的对象,优化效果会更加明显。
  • 深拷贝的开销很大。应尽量减少使用,或考虑基于差异的更新。

总结

Object是JavaScript的核心,提供了对象创建、属性控制、继承、复制、可变性管理等多种功能。了解诸如Object.definePropertyObject.assignObject.freeze等API很重要,并且需要仔细设计,以避免原型污染和浅拷贝等陷阱。

您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。

YouTube Video