Как сделать прототип объекта JavaScript постоянным? - PullRequest
4 голосов
/ 14 апреля 2019

Могу ли я обеспечить, чтобы прототип объекта не был изменен?

Примечание! Существуют некоторые требования:

  • Объект должен вести себя так же, как обычный литерал объекта (добавлять / удалять / настраивать / изменять свойства и дескрипторы объекта),
  • с буквально only новым ограничениемпоскольку прототип является постоянным.

Поэтому, кроме того, что прототип является постоянным, я не хочу добавлять другие ограничения (такие инструменты, как Object.seal/freeze/preventExtensions налагают больше ограничений на объект).

Должен ли я сделать обезьянку-патч ? Object.prototype.__proto__ и Object.setPrototypeOf, чтобы добиться этого?

Ответы [ 2 ]

4 голосов
/ 14 апреля 2019

Один из вариантов: 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' };
0 голосов
/ 15 апреля 2019

Я придумал патч обезьяны, чтобы сделать то, что хотел, выставив функцию lockPrototype в следующем фрагменте.

Единственная известная мне проблема заключается в том, что если какой-то другой код запускается перед моим кодом, то они могут получить исходные ссылки на Object.setPrototypeOf и установщик из дескриптора Object.prototype.__proto__ и, таким образом, обойти мой monkey-patch , Но в большинстве случаев я думаю, что это сработает. У нативной реализации такой проблемы не будет.

Прежде чем я приму свой ответ, есть ли другие проблемы с ним?

Вот пример (часть, помеченная 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:

enter image description here

...