对象

**Object** 类型表示 JavaScript 数据类型 之一。它用于存储各种键控集合和更复杂的实体。可以使用 Object() 构造函数或 对象初始化程序/字面量语法 创建对象。

描述

JavaScript 中几乎所有 对象 都是 Object 的实例;一个典型的对象继承了 Object.prototype 的属性(包括方法),尽管这些属性可能会被隐藏(即被覆盖)。唯一不继承自 Object.prototype 的对象是具有 null 原型 或从其他 null 原型对象派生的对象。

Object.prototype 对象的更改会通过原型链被 **所有** 对象看到,除非这些更改所涉及的属性和方法在原型链中进一步被覆盖。这提供了一种非常强大但可能存在危险的机制来覆盖或扩展对象行为。为了使其更安全,Object.prototype 是核心 JavaScript 语言中唯一具有 不可变原型 的对象——Object.prototype 的原型始终为 null 且不可更改。

对象原型属性

您应该避免从实例中直接调用任何 Object.prototype 方法,尤其是那些不打算多态的方法(即只有其初始行为才有意义,并且没有后代对象可以以有意义的方式覆盖它)。所有从 Object.prototype 派生的对象都可以定义一个具有相同名称的自定义自身属性,但其语义与您期望的完全不同。此外,这些属性不会被 null 原型对象 继承。所有用于处理对象的现代 JavaScript 实用程序都是 静态的。更具体地说

在语义上等效的静态方法不存在的情况下,或者如果您确实想要使用 Object.prototype 方法,您应该直接 call() 目标对象上的 Object.prototype 方法,以防止对象具有覆盖属性从而产生意外结果。

js
const obj = {
  foo: 1,
  // You should not define such a method on your own object,
  // but you may not be able to prevent it from happening if
  // you are receiving the object from external input
  propertyIsEnumerable() {
    return false;
  },
};

obj.propertyIsEnumerable("foo"); // false; unexpected result
Object.prototype.propertyIsEnumerable.call(obj, "foo"); // true; expected result

从对象中删除属性

对象本身没有任何方法可以删除其自身的属性(例如 Map.prototype.delete())。为此,必须使用 delete 运算符。

空原型对象

JavaScript 中几乎所有对象最终都继承自 Object.prototype(请参阅 继承和原型链)。但是,您可以使用 Object.create(null) 或带有 __proto__: null对象初始化程序语法 创建 null 原型对象(注意:对象文字中的 __proto__ 键与已弃用的 Object.prototype.__proto__ 属性不同)。您还可以通过调用 Object.setPrototypeOf(obj, null) 将现有对象的原型更改为 null

js
const obj = Object.create(null);
const obj2 = { __proto__: null };

具有 null 原型的对象的行为可能会出乎意料,因为它不会从 Object.prototype 继承任何对象方法。在调试时尤其如此,因为常见对象属性转换/检测实用程序函数可能会生成错误或丢失信息(尤其是在使用忽略错误的静默错误陷阱时)。

例如,缺少 Object.prototype.toString() 通常会使调试变得难以处理

js
const normalObj = {}; // create a normal object
const nullProtoObj = Object.create(null); // create an object with "null" prototype

console.log(`normalObj is: ${normalObj}`); // shows "normalObj is: [object Object]"
console.log(`nullProtoObj is: ${nullProtoObj}`); // throws error: Cannot convert object to primitive value

alert(normalObj); // shows [object Object]
alert(nullProtoObj); // throws error: Cannot convert object to primitive value

其他方法也会失败。

js
normalObj.valueOf(); // shows {}
nullProtoObj.valueOf(); // throws error: nullProtoObj.valueOf is not a function

normalObj.hasOwnProperty("p"); // shows "true"
nullProtoObj.hasOwnProperty("p"); // throws error: nullProtoObj.hasOwnProperty is not a function

normalObj.constructor; // shows "Object() { [native code] }"
nullProtoObj.constructor; // shows "undefined"

我们可以通过为其分配一个 toString 方法来将其添加回空原型对象。

js
nullProtoObj.toString = Object.prototype.toString; // since new object lacks toString, add the original generic one back

console.log(nullProtoObj.toString()); // shows "[object Object]"
console.log(`nullProtoObj is: ${nullProtoObj}`); // shows "nullProtoObj is: [object Object]"

与普通对象不同,在普通对象中 toString() 位于对象的原型上,此处的 toString() 方法是 nullProtoObj 的自身属性。这是因为 nullProtoObj 没有(null)原型。

您还可以使用 Object.setPrototypeOf(nullProtoObj, Object.prototype) 将空原型对象恢复为普通对象。

在实践中,具有null原型的对象通常用作映射的廉价替代品。Object.prototype属性的存在会导致一些错误。

js
const ages = { alice: 18, bob: 27 };

function hasPerson(name) {
  return name in ages;
}

function getAge(name) {
  return ages[name];
}

hasPerson("hasOwnProperty"); // true
getAge("toString"); // [Function: toString]

使用无原型对象消除了这种风险,而不会给hasPersongetAge函数带来过多的复杂性。

js
const ages = Object.create(null, {
  alice: { value: 18, enumerable: true },
  bob: { value: 27, enumerable: true },
});

hasPerson("hasOwnProperty"); // false
getAge("toString"); // undefined

在这种情况下,应谨慎添加任何方法,因为它们可能与存储为数据的其他键值对混淆。

使您的对象不继承自Object.prototype还可以防止原型污染攻击。如果恶意脚本向Object.prototype添加了一个属性,则它将可以在程序中的每个对象上访问,除了具有空原型的对象。

js
const user = {};

// A malicious script:
Object.prototype.authenticated = true;

// Unexpectedly allowing unauthenticated user to pass through
if (user.authenticated) {
  // access confidential data
}

JavaScript还内置了生成null原型对象的API,特别是那些使用对象作为临时键值集合的API。例如

术语“null原型对象”通常还包括任何在其原型链中没有Object.prototype的对象。使用类时,可以使用extends null创建此类对象。

对象强制转换

许多期望对象的内置操作首先将其参数强制转换为对象。该操作可以概括如下

在JavaScript中可以通过两种方法实现几乎相同的效果。

  • Object.prototype.valueOf()Object.prototype.valueOf.call(x)完全执行上面解释的对象强制转换步骤以转换x
  • Object()函数:Object(x)使用相同的算法转换x,除了undefinednull不会抛出TypeError,而是返回一个普通对象。

使用对象强制转换的位置包括

  • for...in循环的object参数。
  • Array方法的this值。
  • Object方法(如Object.keys())的参数。
  • 当访问基本类型上的属性时进行自动装箱,因为基本类型没有属性。
  • 调用非严格函数时的this值。基本类型会被装箱,而nullundefined会被替换为全局对象

转换为基本类型不同,对象强制转换过程本身无法以任何方式观察到,因为它不会像toStringvalueOf方法那样调用自定义代码。

构造函数

Object()

将输入转换为对象。

静态方法

Object.assign()

将一个或多个源对象的所有可枚举自身属性的值复制到目标对象。

Object.create()

使用指定的原型对象和属性创建一个新对象。

Object.defineProperties()

根据给定的描述符向对象添加命名属性。

Object.defineProperty()

根据给定的描述符向对象添加命名属性。

Object.entries()

返回一个数组,其中包含给定对象**自身**可枚举字符串属性的所有[key, value]对。

Object.freeze()

冻结对象。其他代码无法删除或更改其属性。

Object.fromEntries()

[key, value]对的可迭代对象创建一个新对象。(这是Object.entries的反向操作)。

Object.getOwnPropertyDescriptor()

返回对象上命名属性的属性描述符。

Object.getOwnPropertyDescriptors()

返回一个包含对象所有自身属性描述符的对象。

Object.getOwnPropertyNames()

返回一个数组,其中包含给定对象**自身**可枚举和不可枚举属性的所有名称。

Object.getOwnPropertySymbols()

返回在给定对象上直接找到的所有符号属性的数组。

Object.getPrototypeOf()

返回指定对象的原型(内部[[Prototype]]属性)。

Object.groupBy()

根据提供的回调函数返回的字符串值对给定可迭代对象的元素进行分组。返回的对象为每个组都有一个单独的属性,其中包含该组中元素的数组。

Object.hasOwn()

如果指定对象具有指示的属性作为其**自身**属性,则返回true;如果属性是继承的或不存在,则返回false

Object.is()

比较两个值是否相同。将所有NaN值等同起来(这与==使用的IsLooselyEqual===使用的IsStrictlyEqual都不同)。

Object.isExtensible()

确定是否允许扩展对象。

Object.isFrozen()

确定对象是否已冻结。

Object.isSealed()

确定对象是否已密封。

Object.keys()

返回一个数组,其中包含给定对象**自身**可枚举字符串属性的所有名称。

Object.preventExtensions()

防止对对象进行任何扩展。

Object.seal()

防止其他代码删除对象的属性。

Object.setPrototypeOf()

设置对象的原型(其内部[[Prototype]]属性)。

Object.values()

返回一个数组,其中包含对应于给定对象**自身**可枚举字符串属性的所有值。

实例属性

这些属性定义在Object.prototype上,并由所有Object实例共享。

Object.prototype.__proto__ 已弃用

指向在实例化对象时用作原型的对象。

Object.prototype.constructor

创建实例对象的构造函数。对于普通Object实例,初始值为Object构造函数。其他构造函数的实例都从其各自的Constructor.prototype对象继承constructor属性。

实例方法

Object.prototype.__defineGetter__() 已弃用

将函数与属性关联,当访问该属性时,执行该函数并返回其返回值。

Object.prototype.__defineSetter__() 已弃用

将函数与属性关联,当设置该属性时,执行该函数以修改属性。

Object.prototype.__lookupGetter__() 已弃用

返回绑定到指定属性的 getter 函数。

Object.prototype.__lookupSetter__() 已弃用

返回绑定到指定属性的 setter 函数。

Object.prototype.hasOwnProperty()

返回一个布尔值,指示对象是否包含指定的属性作为该对象的直接属性,而不是通过原型链继承的属性。

Object.prototype.isPrototypeOf()

返回一个布尔值,指示调用此方法的对象是否在指定对象的原型链中。

Object.prototype.propertyIsEnumerable()

返回一个布尔值,指示指定的属性是否是对象的可枚举自身属性。

Object.prototype.toLocaleString()

调用toString()

Object.prototype.toString()

返回对象的字符串表示形式。

Object.prototype.valueOf()

返回指定对象的原始值。

示例

构造空对象

以下示例使用new关键字和不同的参数创建空对象。

js
const o1 = new Object();
const o2 = new Object(undefined);
const o3 = new Object(null);

使用Object()构造函数将基本类型转换为其相应类型的Object

您可以使用Object()构造函数创建基本值的Object包装器。

以下示例创建变量o1o2,它们是存储BooleanBigInt值的Object。

js
// Equivalent to const o1 = new Boolean(true)
const o1 = new Object(true);

// No equivalent because BigInt() can't be called as a constructor,
// and calling it as a regular function won't create an object
const o2 = new Object(1n);

对象原型

在更改现有Object.prototype方法的行为时,请考虑通过在现有逻辑之前或之后包装扩展来注入代码。例如,此(未经测试)代码将在内置逻辑或其他人的扩展执行之前执行自定义逻辑。

在使用钩子修改原型时,通过对函数调用apply()this和参数(调用状态)传递给当前行为。此模式可用于任何原型,例如Node.prototypeFunction.prototype等。

js
const current = Object.prototype.valueOf;

// Since my property "-prop-value" is cross-cutting and isn't always
// on the same prototype chain, I want to modify Object.prototype:
Object.prototype.valueOf = function (...args) {
  if (Object.hasOwn(this, "-prop-value")) {
    return this["-prop-value"];
  } else {
    // It doesn't look like one of my objects, so let's fall back on
    // the default behavior by reproducing the current behavior as best we can.
    // The apply behaves like "super" in some other languages.
    // Even though valueOf() doesn't take arguments, some other hook may.
    return current.apply(this, args);
  }
};

警告:修改任何内置构造函数的prototype属性被认为是不好的做法,并且会影响前向兼容性。

您可以在继承和原型链中阅读有关原型的更多信息。

规范

规范
ECMAScript语言规范
# sec-object-objects

浏览器兼容性

BCD表格仅在启用JavaScript的浏览器中加载。

另请参阅