How to make a JavaScript object's prototype pe

2019-06-27 11:20发布

Can I enforce that the prototype of an object not be changed?

Note! There are some requirements:

  • The object should behave just like a regular object literal (add/remove/configure/modify properties and descriptors on the object),
  • with literally the only new limitation being that the prototype is permanent.

So other than the prototype being permanent, I don't want to add any other limitations (tools like Object.seal/freeze/preventExtensions impose more limitations on the object).

Would I have to monkey-patch

2条回答
Juvenile、少年°
2楼-- · 2019-06-27 11:45

I came up with a monkey-patch to do what I wanted, exposing a lockPrototype function in the following snippet.

The only problem that I am aware of is that if some other code runs before my code, then they can get original references to Object.setPrototypeOf and the setter from the Object.prototype.__proto__ descriptor, and thus work around my monkey-patch. But for most cases I think it would work. A native implementation wouldn't have this problem.

Before I accept my own answer, are there any other problems with it?

Here's the example (the part labeled lockPrototype.js is the implementation, and the part labeled test.js is how it could be used):

// 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

(fiddle: https://jsfiddle.net/trusktr/Lnrfoj0u)

The output that you should see in Chrome devtools console is:

enter image description here

查看更多
闹够了就滚
3楼-- · 2019-06-27 12:01

One option is Object​.prevent​Extensions() (note, this locks the whole object from extensions, doesn't lock only the prototype from being modified):

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

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

查看更多
登录 后发表回答