Моя цель - создать вызываемые JavaScript объекты (функции), которые имеют настраиваемые [[Prototype]]
, а могут (или не могут) наследовать от Function.prototype
.
Этот вопрос задавали много раз (даже при переполнении стека, например this , this или this ), но ни у одного из них нет хороших решений (у некоторых нет ответы, в то время как у других есть решения, перечисленные ниже).
Некоторые способы пришли мне в голову. Вот они:
Использование Object.create
:
Это стандартный способ создания пользовательских прототипов объектов. Однако он не может создавать вызываемые объекты (функции).
Создание подклассов Function
:
Это, вероятно, самый простой способ, о котором думает большинство людей . Примерно так:
class MyFunction extends Function{
constructor(id){
super(
'...args',
'return args.length'
)
this.id=id
}
logCall(...args){
console.log('Calling MyFunction #%i with arguments: %o', this.id, args)
return Reflect.apply(this, this, args)
}
}
const fn = new MyFunction(1)
console.log(fn('foo', 'bar')) //2
fn.logCall('foo', 'bar', 'baz') //Calling MyFunction #1 with arguments: ['foo', 'bar', 'baz']
//Works, so far so good!
Однако у этого есть несколько недостатков:
- Использует
eval
-like Function
конструктор - Компиляция во время выполнения тела функции, каждый раз, когда создается новый экземпляр
- Отсутствие доступа к лексической / закрывающей области внутри тела функции
- Отсутствие ошибок времени компиляции и подсветки синтаксиса в теле функции: сложнее для поддержания
- Невозможно создать функции, которые не имеют
Function.prototype
(и, следовательно, Object.prototype
) в их цепочке прототипов
Object.setPrototypeOf()
:
class MyFunction{
constructor(id){
const _this = function (...args){
return args.length
}
Object.setPrototypeOf(_this, Object.getPrototypeOf(this))
_this.id=id
return _this
}
logCall(...args){
console.log('Calling MyFunction #%i with arguments: %o', this.id, args)
return Reflect.apply(this, this, args)
}
}
const fn = new MyFunction(1)
console.log(fn('foo', 'bar')) //2
fn.logCall('foo', 'bar', 'baz') //Calling MyFunction #1 with arguments: ['foo', 'bar', 'baz']
//Works as well, so far so good!
Минусы:
Страшное красное предупреждение на MDN о производительности:
Предупреждение: Изменение [[Prototype]]
объекта - это по своей природе то, как современные JavaScript механизмы оптимизируют доступ к свойствам , в настоящее время очень медленная операция во всех браузерах и JavaScript двигатель. Кроме того, эффекты изменения наследования тонкие и обширные и не ограничиваются просто временем, затраченным на инструкцию Object.setPrototypeOf(...)
, но могут распространяться на любой код , который имеет доступ к любому объекту, [[Prototype]]
которого был изменен.
Поскольку эта функция является частью языка, разработчикам движка по-прежнему приходится выполнять эту функцию эффективно (в идеале). Пока разработчики движка не решат эту проблему, если вы беспокоитесь о производительности, вам следует избегать установки [[Prototype]]
объекта. Вместо этого создайте новый объект с желаемым [[Prototype]]
, используя Object.create()
.
Труднее понять, unsemanti c, hacky -просмотр кода
Proxy
с фальшивыми ловушками:
class MyFunction{
constructor(id){
const _this = new Proxy(
function (...args){
return args.length
},
Object.assign(
Reflect
.ownKeys(Reflect)
.reduce((handler, trap) => ( //Forward operations to real `this`
handler[trap] = (...args) => Reflect[trap](this, ...args.slice(1)),
handler
), {}),
{
apply(...args){
return Reflect.apply(...args)
}
}
)
)
_this.id=id
return _this
}
logCall(...args){
console.log('Calling MyFunction #%i with arguments: %o', this.id, args)
return Reflect.apply(this, this, args)
}
}
const fn = new MyFunction(1)
console.log(fn('foo', 'bar')) //2
fn.logCall('foo', 'bar', 'baz') //Calling MyFunction #1 with arguments: ['foo', 'bar', 'baz']
//Works as well, so far so good!
Недостатки:
Итак, что касается Я знаю, нет хорошего способа сделать это.
Мой вопрос:
Есть ли альтернатива , которая лучше этих, или, если ее нет, какой из них имеет наибольшие преимущества с точки зрения производительности , удобочитаемости и поддержки?