好的,我们来深入且系统地讲解 Object.create() 函数,并梳理 Object 上的其他核心函数。
Object.create() 的深入解析
Object.create() 是一个非常基础且强大的方法,它允许我们创建一个新对象,并精确地指定这个新对象的原型(prototype)。这是理解 Object.create() 的核心。相较于通过 new 关键字或对象字面量 {} 创建对象,Object.create() 给了你对原型链最直接的控制权。
语法
JavaScript
Object.create(proto, propertiesObject)
它接收两个参数:
-
proto: 必需参数。一个对象,它将成为新创建对象的原型。它可以是任何对象,甚至可以是null。 -
propertiesObject: 可选参数。一个对象,用于为新对象定义额外的自有属性(own properties),其格式与Object.defineProperties()的第二个参数相同。
核心用法与场景
1. 实现纯粹的原型式继承
这是 Object.create() 最经典、最主要的用途。它能让你清晰地建立对象间的继承关系。
假设我们有一个“动物”原型,它有一个 breathe 方法:
JavaScript
const animal = {
isAlive: true,
breathe() {
console.log('inhale, exhale');
}
};
// 创建一个 dog 对象,它的原型是 animal
const dog = Object.create(animal);
dog.breed = 'Labrador'; // 为 dog 添加自有属性
console.log(dog.isAlive); // true (继承自 animal)
dog.breathe(); // "inhale, exhale" (继承自 animal)
console.log(dog.hasOwnProperty('breed')); // true (breed 是 dog 自己的属性)
console.log(dog.hasOwnProperty('isAlive')); // false (isAlive 是原型链上的属性)
背后发生了什么?
dog 对象本身是空的(除了我们后来添加的 breed 属性)。当我们访问 dog.isAlive 时,JavaScript 引擎在 dog 对象上找不到它,于是就会沿着它的原型链向上查找,在 animal 对象上找到了 isAlive 并返回其值。这个查找链条是:dog ---> animal ---> Object.prototype ---> null。
2. 创建一个“纯净”的对象 (Object.create(null))
这是一个非常重要的特例。通常,我们用 {} 创建的对象,其原型默认是 Object.prototype,因此它天生就带有一些方法,如 toString(), hasOwnProperty() 等。
但在某些场景下,我们希望创建一个完全干净、不继承任何东西的对象,把它当作一个纯粹的“哈希表”或“字典”来使用,以避免潜在的键名冲突。
JavaScript
// 创建一个“纯净”的 map 对象
const map = Object.create(null);
map.name = 'My Map';
map.toString = 'a string'; // 这里的 toString 只是一个普通的属性键
console.log(map.name); // "My Map"
// 因为 map 的原型是 null,它没有任何继承的方法
// console.log(map.toString()); // 这会抛出错误: Uncaught TypeError: map.toString is not a function
console.log(map['toString']); // "a string"
// 同样,它也没有 hasOwnProperty 方法
// console.log(map.hasOwnProperty('name')); // 错误: map.hasOwnProperty is not a function
为什么这很有用?
如果用户输入的数据中恰好有一个键是 hasOwnProperty,而你又把它存入一个普通对象 let obj = {} 中,当你调用 obj.hasOwnProperty 时,你可能无法区分这是用户数据还是原型上的方法。使用 Object.create(null) 创建的对象则完全没有这个问题。
3. 使用第二个参数定义属性
第二个可选参数允许你在创建对象时,就以非常精确的方式定义其自有属性。这提供了比简单赋值更强大的控制力。
属性描述符(Property Descriptors)有以下几个键:
-
value: 属性的值。 -
writable: 是否可写(true/false)。 -
enumerable: 是否可被for...in循环或Object.keys()枚举(true/false)。 -
configurable: 是否可被删除或更改其特性(true/false)。
JavaScript
const user = Object.create(null, {
// 定义一个数据属性
name: {
value: 'John Doe',
writable: true,
enumerable: true
},
// 定义一个只读的、不可枚举的属性
id: {
value: 123,
writable: false,
enumerable: false
}
});
console.log(user.name); // "John Doe"
user.name = 'Jane Doe'; // 可以修改
console.log(user.name); // "Jane Doe"
console.log(user.id); // 123
// user.id = 456; // 在严格模式下会报错,非严格模式下静默失败
console.log(Object.keys(user)); // ["name"] (因为 id 的 enumerable 是 false)
Object 上的其他重要静态函数
Object 构造函数本身也挂载了许多非常有用的静态方法。我们可以将它们分类来理解。
一、 对象的创建与修改
除了 Object.create(),还有:
-
Object.assign(target, ...sources)-
作用:将一个或多个源对象(
sources)的所有可枚举的自有属性复制到目标对象(target)。它会返回修改后的target对象。 -
关键点:这是一个浅拷贝(Shallow Copy)。如果源对象的属性值是另一个对象,那么复制的是该对象的引用,而不是对象本身。
-
示例:
JavaScript
const defaults = { a: 1, b: 2 }; const options = { b: 3, c: 4 }; const merged = Object.assign({}, defaults, options); // { a: 1, b: 3, c: 4 } // options.b 覆盖了 defaults.b
-
-
Object.defineProperty(obj, prop, descriptor)-
作用:在一个对象上直接定义一个新属性,或修改一个现有属性。
-
关键点:提供对属性特性的精细控制(writable, enumerable, configurable)。这是实现数据劫持(如 Vue 2.x)的基础。
-
-
Object.defineProperties(obj, props)- 作用:在一个对象上定义或修改多个属性。
Object.create()的第二个参数内部就使用了这个逻辑。
- 作用:在一个对象上定义或修改多个属性。
二、 获取对象的属性信息(自省)
这些方法用于检查对象自身的结构。
-
Object.keys(obj)- 作用:返回一个数组,包含对象自身的所有可枚举属性的键名(字符串)。
-
Object.values(obj)- 作用:返回一个数组,包含对象自身的所有可枚举属性的值。
-
Object.entries(obj)-
作用:返回一个数组,每个成员是对象自身的一个
[key, value]键值对数组。对于将对象转换为Map或进行for...of循环非常有用。 -
示例:
JavaScript
const obj = { a: 1, b: 2 }; for (const [key, value] of Object.entries(obj)) { console.log(`${key}: ${value}`); }
-
-
Object.getOwnPropertyNames(obj)- 作用:返回一个数组,包含对象自身的**所有属性(包括不可枚举的属性,但不包括 Symbol 属性)**的键名。
-
Object.getOwnPropertySymbols(obj)- 作用:返回一个数组,包含对象自身的所有 Symbol 属性。
-
Object.getOwnPropertyDescriptor(obj, prop)- 作用:返回指定对象上一个自有属性的属性描述符。如果属性不存在,则返回
undefined。
- 作用:返回指定对象上一个自有属性的属性描述符。如果属性不存在,则返回
-
Object.getOwnPropertyDescriptors(obj)- 作用:返回一个对象,包含了指定对象所有自有属性的描述符。
三、 原型链的操作
-
Object.getPrototypeOf(obj)- 作用:返回指定对象的原型。这是获取一个对象原型的标准、推荐方法(优于非标准的
__proto__)。
- 作用:返回指定对象的原型。这是获取一个对象原型的标准、推荐方法(优于非标准的
-
Object.setPrototypeOf(obj, prototype)-
作用:设置一个对象的原型。
-
注意:这是一个非常消耗性能的操作,因为它会改变对象的整个继承链。在性能敏感的代码中应避免使用。最好在对象创建之初就用
Object.create()来确定其原型。
-
四、 控制对象的状态(使其不可变)
这些方法用于锁定对象,防止其被修改。
-
Object.preventExtensions(obj)-
作用:让一个对象不能再添加新的属性。已有属性仍然可以被修改或删除。
-
检查方法:
Object.isExtensible(obj)
-
-
Object.seal(obj)-
作用:密封一个对象。这会
preventExtensions,并且把所有现有属性标记为configurable: false。这意味着你不能添加新属性,也不能删除或重新配置现有属性,但可以修改现有属性的值(如果它们是writable: true的话)。 -
检查方法:
Object.isSealed(obj)
-
-
Object.freeze(obj)-
作用:冻结一个对象。这是最严格的级别,它会
seal对象,并将其所有数据属性的writable特性设为false。对象将变得完全不可变(仅限第一层,是浅冻结)。 -
检查方法:
Object.isFrozen(obj)
-
五、 实用工具
-
Object.is(value1, value2)-
作用:判断两个值是否是相同的值。它比
===更严格。 -
与
===的区别:-
Object.is(NaN, NaN)返回true(而NaN === NaN返回false)。 -
Object.is(+0, -0)返回false(而+0 === -0返回true)。
-
-
-
Object.fromEntries(iterable)-
作用:
Object.entries()的逆操作。它接收一个[key, value]形式的迭代器(如数组或Map),并返回一个新的对象。 -
示例:
JavaScript
const entries = new Map([ ['foo', 'bar'], ['baz', 42] ]); const obj = Object.fromEntries(entries); // { foo: "bar", baz: 42 }
-
总结
Object 上的这些静态方法为 JavaScript 开发者提供了操作和理解这门语言核心——对象的全套工具。
-
Object.create()是基石,它让你从原型这个根源上控制对象的创建。 -
属性访问系列 (
keys,values,entries等) 提供了标准化的方式来迭代和检视对象内容。 -
属性定义系列 (
defineProperty,defineProperties) 提供了对属性底层特性的精细控制。 -
原型操作系列 (
getPrototypeOf,setPrototypeOf) 提供了管理继承链的能力。 -
对象状态控制系列 (
freeze,seal,preventExtensions) 提供了创建不可变数据的原生支持。 -
实用工具 (
assign,is,fromEntries) 简化了许多常见的编程任务。
理解并熟练运用这些方法,是深入掌握 JavaScript 面向对象编程和函数式编程范式的关键。