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
- 使用
getter和setter,可以将属性访问视为外部API,并添加校验或副作用。
原型与继承(prototype / __proto__ / Object.getPrototypeOf)
JavaScript中的继承基于原型链,而不是类。对象可以引用其他对象作为它们的原型。
Object.getPrototypeOf 和 Object.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
hasOwnProperty、isPrototypeOf、toString 和 valueOf 定义了对象的基本行为。
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.keys、Object.values和Object.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.freeze、Object.seal和Object.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.defineProperty、Object.assign和Object.freeze等API很重要,并且需要仔细设计,以避免原型污染和浅拷贝等陷阱。
您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。