【问题标题】:How to make a JavaScript object's prototype permanent?如何使 JavaScript 对象的原型永久化?
【发布时间】:2019-04-14 19:01:02
【问题描述】:

我可以强制不改变对象的原型吗?

注意!有一些要求:

  • 对象的行为应该与常规对象字面量一样(添加/删除/配置/修改对象的属性和描述符),
  • 实际上唯一的新限制是原型是永久性的。

所以除了原型是永久性的,我不想添加任何其他限制(Object.seal/freeze/preventExtensions 等工具对对象施加更多限制)。

我需要猴子补丁吗???? Object.prototype.__proto__Object.setPrototypeOf 以实现此目的?

【问题讨论】:

  • 您是否真的错过了Object.seal 或者由于某种原因它不适合您?此外,Object.freeze 更适合您。
  • @WiktorZychla 啊哈!我没有尝试过,但我的直觉并不认为这会起作用,因为我认为 __proto__ 是一个 getter/setter 意味着 setter 仍然可以接受这个值并按照它的意愿去做(就像在用户代码设置器)!谢谢!这是否意味着用户代码设置者应该检查对象是否被密封,并在这种情况下抛出?否则,为什么 Object.seal 禁用__proto__,而不禁用常规用户设置器?
  • @WiktorZychla 提出了一个新问题:stackoverflow.com/questions/55679182
  • __proto__ 已弃用。不要再使用了。
  • @trusktr 仅指定在 web-compat 环境之间具有一致性。 It is officially specified as deprecated.

标签: javascript prototype prototype-programming prototype-chain


【解决方案1】:

一个选项是Object​.prevent​Extensions() (注意,这会锁定整个对象的扩展,不会只锁定原型被修改):

'use strict';
const obj = {};
Object.preventExtensions(obj);
Object.setPrototypeOf(obj, { possibleNewPrototype: 'foo' });

'use strict';
const obj = {};
Object.preventExtensions(obj);
obj.__proto__ = { possibleNewPrototype: 'foo' };

【讨论】:

  • 我没有将其标记为答案,因为我需要对象的行为与常规对象文字(添加/删除属性)一样,唯一的限制是原型是永久性的。
  • 有了这个限制,你正在寻找的东西isn't possible - SetPrototypeOf 内部方法可用于更改对象的底层原型当且仅当 对象仍然是可扩展的(如果不可扩展,也不能添加其他属性)。
  • 我用猴子补丁方法发布了一个答案。有没有我可能遗漏的问题或问题?
  • 在使用 TypeScript 一段时间后,我开始喜欢防止扩展的想法,因为它可以强制类型安全。如果我的用户真的想将新数据与对象相关联,他们可以使用 Map 或 WeakMap。我相信这将有助于减少易破解的代码。防止扩展的想法在我心中萌生了。
【解决方案2】:

我想出了一个猴子补丁来做我想做的事,在下面的 sn-p 中公开一个 lockPrototype 函数。

我知道的唯一问题是,如果其他一些代码在我的代码之前运行,那么它们可以从Object.prototype.__proto__ 描述符中获取对Object.setPrototypeOf 和setter 的原始引用,从而解决我的猴子补丁.但在大多数情况下,我认为它会起作用。原生实现不会有这个问题。

在我接受自己的答案之前,是否还有其他问题?

这是示例(标记为lockPrototype.js 的部分是实现,标记为test.js 的部分是如何使用它):

// lockPrototype.js /////////////////////////////////////////////////////
const oldSetPrototypeOf = Object.setPrototypeOf
const lockedObjects = new WeakSet

Object.setPrototypeOf = function(obj, proto) {
  if (lockedObjects.has(obj))
    throw new TypeError("#<Object>'s prototype is locked.")

  oldSetPrototypeOf.call(Object, obj, proto)
}

const __proto__descriptor = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__')

Object.defineProperty(Object.prototype, '__proto__', {
  ...__proto__descriptor,

  set(proto) {
    if (lockedObjects.has(this))
      throw new TypeError("#<Object>'s prototype is locked.")

    __proto__descriptor.set.call(this, proto)
  },
})

// export
function lockPrototype(obj) {

  // this behavior is similar to Object.seal/freeze/preventExtensions
  if (typeof obj !== "object" && typeof obj !== "function")
    return obj

  lockedObjects.add(obj)
}




// test.js //////////////////////////////////////////////////////////////
// import {lockPrototype} from './lockPrototype'

const a = {}
const b = {}
const c = {}
const proto = { n: 5 }

lockPrototype(b)
lockPrototype(c)

Object.setPrototypeOf(a, proto) // works fine

console.log('a.n', a.n) // 5

setTimeout(() => {
  console.log('b.n:', b.n) // undefined

  setTimeout(() => {
    console.log('c.n', c.n) // undefined
  })

  c.__proto__ = proto // throws
})

Object.setPrototypeOf(b, proto) // throws

(小提琴:https://jsfiddle.net/trusktr/Lnrfoj0u

您应该在 Chrome devtools 控制台中看到的输出是:

【讨论】:

    猜你喜欢
    • 2018-09-06
    • 2021-02-01
    • 2011-11-07
    • 2017-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-15
    • 1970-01-01
    相关资源
    最近更新 更多