Object
Baseline
Widely available
*
This feature is well established and works across many devices and browser versions. It’s been available across browsers since 2015年7月.
* Some parts of this feature may have varying levels of support.
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 中用于操作对象的工具方法都是静态的。更具体地说:
valueOf()、toString()和toLocaleString()存在的目的是为了多态化,你应该期望对象会定义自己的实现并具有合理的行为,因此你可以将它们作为实例方法调用。但是,valueOf()和toString()通常是通过强制类型转换隐式调用的,因此你不需要在代码中自己调用它们。__defineGetter__()、__defineSetter__()、__lookupGetter__()和__lookupSetter__()已被弃用,不应该再使用。请使用静态方法Object.defineProperty()和Object.getOwnPropertyDescriptor()作为替代。__proto__属性已被弃用,不应该再使用。请使用静态方法Object.getPrototypeOf()和Object.setPrototypeOf()作为替代。propertyIsEnumerable()和hasOwnProperty()方法可以分别用静态方法Object.getOwnPropertyDescriptor()和Object.hasOwn()替换。- 如果你正在检查一个构造函数的
prototype属性,通常可以用instanceof代替isPrototypeOf()方法。
如果不存在语义上等价的静态方法,或者你真的想使用 Object.prototype 方法,你应该通过 call() 直接在目标对象上调用 Object.prototype 方法,以防止因目标对象上原有方法被重写而产生意外的结果。
const obj = {
foo: 1,
// 如果可能的话,你不应该在自己的对象上定义这样的方法,
// 但是如果你从外部输入接收对象,可能无法防止这种情况的发生
propertyIsEnumerable() {
return false;
},
};
obj.propertyIsEnumerable("foo"); // false;预期外的结果
Object.prototype.propertyIsEnumerable.call(obj, "foo"); // true;预期的结果
从对象中删除属性
一个对象本身没有任何方法可以(像 Map.prototype.delete() 一样)删除自己的属性。要删除一个对象的属性,必须使用 delete 运算符。
null 原型对象
几乎所有的 JavaScript 对象最终都继承自 Object.prototype(参见继承与原型链)。然而,你可以使用 Object.create(null) 或定义了 __proto__: null 的对象字面量语法(注意:对象字面量中的 __proto__ 键不同于已弃用的 Object.prototype.__proto__ 属性)来创建 null 原型对象。你还可以通过调用 Object.setPrototypeOf(obj, null) 将现有对象的原型更改为 null。
const obj = Object.create(null);
const obj2 = { __proto__: null };
null 原型对象可能会有一些预期外的行为表现,因为它不会从 Object.prototype 继承任何对象方法。这在调试时尤其需要注意,因为常见的对象属性转换/检测实用方法可能会产生错误或丢失信息(特别是在使用了忽略错误的静默错误捕获机制的情况下)。
例如,Object.prototype.toString() 方法的缺失通常会使得调试变得困难:
const normalObj = {}; // 创建一个普通对象
const nullProtoObj = Object.create(null); // 创建一个 "null" 原型对象
console.log(`normalObj 是:${normalObj}`); // 显示 "normalObj 是:[object Object]"
console.log(`nullProtoObj 是:${nullProtoObj}`); // 抛出错误:Cannot convert object to primitive value
alert(normalObj); // 显示 [object Object]
alert(nullProtoObj); // 抛出错误:Cannot convert object to primitive value
其他方法也会失败。
normalObj.valueOf(); // 显示 {}
nullProtoObj.valueOf(); // 抛出错误:nullProtoObj.valueOf is not a function
normalObj.hasOwnProperty("p"); // 显示 "true"
nullProtoObj.hasOwnProperty("p"); // 抛出错误:nullProtoObj.hasOwnProperty is not a function
normalObj.constructor; // 显示 "Object() { [native code] }"
nullProtoObj.constructor; // 显示 "undefined"
我们可以通过为 null 原型对象分配属性的方式将 toString 方法添加回去:
nullProtoObj.toString = Object.prototype.toString; // 由于新对象缺少 `toString` 方法,因此需要将原始的通用 `toString` 方法添加回来。
console.log(nullProtoObj.toString()); // 显示 "[object Object]"
console.log(`nullProtoObj 是:${nullProtoObj}`); // 显示 "nullProtoObj 是:[object Object]"
普通对象的 toString() 方法是在对象的原型上的,而与普通对象不同的是,这里的 toString() 方法是 nullProtoObj 的自有属性。这是因为 nullProtoObj 没有原型(即为 null)。
在实践中,null 原型对象通常被用作 map 的简单替代品。由于存在 Object.prototype 属性,会导致一些错误:
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]
使用一个 null 原型对象可以消除这种风险,同时不会令 hasPerson 和 getAge 函数变得复杂:
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 添加一个属性,程序中的每个对象上都可访问它,除了那些原型为 null 的对象。
const user = {};
// 恶意脚本:
Object.prototype.authenticated = true;
// 意外允许未经身份验证的用户通过
if (user.authenticated) {
// 访问机密数据
}
JavaScript 还具有内置的 API,用于生成 null 原型对象,特别是那些将对象用作临时键值对集合的 API。例如:
Object.groupBy()方法的返回值RegExp.prototype.exec()方法返回结果中的groups和indices.groups属性Array.prototype[Symbol.unscopables]属性(所有[Symbol.unscopables]对象原型都应该为null)import.meta对象- 通过