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:
