如何通过 Object.create(Object.prototype) 构建具有标准功能且属性可控的独立对象?
- 内容介绍
- 相关推荐
本文共计801个文字,预计阅读时间需要4分钟。
使用 `Object.create(Object.prototype)` 可以创建一个干净的普通对象:
为什么不用 {} 或 new Object()?
看似等价,但有细微却关键的区别:
-
{}和new Object()创建的对象,其内部原型确实是Object.prototype,但它们依赖于当前运行时中Object.prototype的状态 —— 如果该原型被篡改(如有人执行了Object.prototype.foo = ...),这些字面量对象也会继承这个污染属性; - 而
Object.create(Object.prototype)明确指定原型,语义更清晰,且在严格模式或某些沙箱环境中更可控; - 更重要的是,它不触发构造函数逻辑(无
Object调用开销,也避免潜在副作用)。
如何让属性“受控”?
“受控”指对属性的添加、修改、删除行为进行显式约束。核心是配合 Object.defineProperty 或 Object.defineProperties,结合描述符(descriptor)精确控制每个属性的行为:
- 设
writable: false防止值被重赋; - 设
enumerable: false让属性不出现在for...in或Object.keys()中; - 设
configurable: false锁定属性本身(无法删、无法改描述符); - 若需只读访问,可定义
get函数,省略set(或设为抛错)。
例如:
const controlled = Object.create(Object.prototype); Object.defineProperties(controlled, { id: { value: 123, writable: false, enumerable: true, configurable: false }, name: { get() { return this._name; }, set(val) { if (typeof val === 'string' && val.trim()) { this._name = val.trim(); } else { throw new TypeError('name must be a non-empty string'); } }, enumerable: true } });
注意原型链与属性查找的关系
即使对象自身属性受控,仍可通过原型链访问 Object.prototype 上的方法(如 controlled.toString())。但若想进一步隔离——比如禁用某些方法(如禁止 hasOwnProperty 被调用),就不能直接用 Object.prototype 作原型,而应使用一个“精简原型”,例如:
const safeProto = {}; safeProto.toString = Object.prototype.toString; safeProto.valueOf = Object.prototype.valueOf; // 不挂载 hasOwnProperty / isPrototypeOf / propertyIsEnumerable 等 const ultraControlled = Object.create(safeProto);
这样既保留基础类型识别能力,又规避了部分可能被滥用的原型方法。
实际使用中的推荐组合
多数场景下,兼顾标准性与可控性的做法是:
- 用
Object.create(Object.prototype)创建空对象; - 用
Object.defineProperties一次性定义所有初始属性,明确各描述符; - 后续新增属性时,始终通过
Object.defineProperty添加,并保持一致策略; - 必要时封装为工厂函数,复用受控逻辑。
例如:
function createControlledUser(data) { const obj = Object.create(Object.prototype); Object.defineProperties(obj, { uid: { value: data.uid, writable: false, configurable: false }, role: { get() { return this._role || 'user'; }, set(v) { this._role = ['admin', 'user', 'guest'].includes(v) ? v : 'user'; } } }); return obj; }
本文共计801个文字,预计阅读时间需要4分钟。
使用 `Object.create(Object.prototype)` 可以创建一个干净的普通对象:
为什么不用 {} 或 new Object()?
看似等价,但有细微却关键的区别:
-
{}和new Object()创建的对象,其内部原型确实是Object.prototype,但它们依赖于当前运行时中Object.prototype的状态 —— 如果该原型被篡改(如有人执行了Object.prototype.foo = ...),这些字面量对象也会继承这个污染属性; - 而
Object.create(Object.prototype)明确指定原型,语义更清晰,且在严格模式或某些沙箱环境中更可控; - 更重要的是,它不触发构造函数逻辑(无
Object调用开销,也避免潜在副作用)。
如何让属性“受控”?
“受控”指对属性的添加、修改、删除行为进行显式约束。核心是配合 Object.defineProperty 或 Object.defineProperties,结合描述符(descriptor)精确控制每个属性的行为:
- 设
writable: false防止值被重赋; - 设
enumerable: false让属性不出现在for...in或Object.keys()中; - 设
configurable: false锁定属性本身(无法删、无法改描述符); - 若需只读访问,可定义
get函数,省略set(或设为抛错)。
例如:
const controlled = Object.create(Object.prototype); Object.defineProperties(controlled, { id: { value: 123, writable: false, enumerable: true, configurable: false }, name: { get() { return this._name; }, set(val) { if (typeof val === 'string' && val.trim()) { this._name = val.trim(); } else { throw new TypeError('name must be a non-empty string'); } }, enumerable: true } });
注意原型链与属性查找的关系
即使对象自身属性受控,仍可通过原型链访问 Object.prototype 上的方法(如 controlled.toString())。但若想进一步隔离——比如禁用某些方法(如禁止 hasOwnProperty 被调用),就不能直接用 Object.prototype 作原型,而应使用一个“精简原型”,例如:
const safeProto = {}; safeProto.toString = Object.prototype.toString; safeProto.valueOf = Object.prototype.valueOf; // 不挂载 hasOwnProperty / isPrototypeOf / propertyIsEnumerable 等 const ultraControlled = Object.create(safeProto);
这样既保留基础类型识别能力,又规避了部分可能被滥用的原型方法。
实际使用中的推荐组合
多数场景下,兼顾标准性与可控性的做法是:
- 用
Object.create(Object.prototype)创建空对象; - 用
Object.defineProperties一次性定义所有初始属性,明确各描述符; - 后续新增属性时,始终通过
Object.defineProperty添加,并保持一致策略; - 必要时封装为工厂函数,复用受控逻辑。
例如:
function createControlledUser(data) { const obj = Object.create(Object.prototype); Object.defineProperties(obj, { uid: { value: data.uid, writable: false, configurable: false }, role: { get() { return this._role || 'user'; }, set(v) { this._role = ['admin', 'user', 'guest'].includes(v) ? v : 'user'; } } }); return obj; }

