Как отметили вы и ваши комментарии, попытка вызова конструкторов классов с пользовательским this
контекстом - это на самом деле не то, что вы хотите попробовать, если есть какой-то способ обойти это.Это было сделано намеренно!
Если по каким-то причинам это неизбежно, чтобы оправдать хитрые обходные пути, вы можете найти два частичных решения ниже.Они оба несовершенны по-своему - в зависимости от вашей конкретной ситуации, один из них все еще может соответствовать вашим потребностям.
Обходной путь 1
Хотя установить this
напрямую невозможнов вызове конструктора можно установить прототип this
для объекта по вашему выбору .
. Для этого вы можете использовать Reflect.construct()
для вызова внутреннего * 1020.* метод с пользовательским значением new.target
.this
будет инициализирован для объекта, унаследованного от new.target.prototype
.
Построение по вашему примеру:
function ES5() {
this.foo = 'foo';
}
class ES6 {
constructor() {
this.bar = 'bar';
}
}
let b = new ES5();
function TemporaryHelperConstructor() {}
TemporaryHelperConstructor.prototype = b;
b = Reflect.construct( ES6, [], TemporaryHelperConstructor ); // The third argument corresponds to the value of new.target
console.log( b.foo + b.bar ); // foobar !
(Точная работа Reflect.construct()
и внутренний метод [[Construct]]
описаны в разделах 26.1.2 и 9.2.2 спецификаций)
Потенциальные проблемы
- На самом деле конструктор класса не вызывается с
this
, связанным с b
, он вызывается с this
, связанным спустой объект, напрямую наследуемый от b
.Это может привести к проблемам, если вы или конструктор класса полагаетесь на такие методы, как Object.getOwnPropertyNames()
, Object.getPrototypeOf()
и т. Д.
Обходной путь 2
Хотя невозможно вызватьвнутренний метод [[Call]]
конструктора класса, не вызывая TypeError
, , можно извлечь блок кода, прикрепленный к конструктору класса, и создать из него обычную функцию, которую затем можно вызвать с помощью пользовательскогоthis
привязка.
Вы можете использовать метод Function.prototype.toString()
для извлечения блока кода конструктора класса в виде строки.Затем конструктор Function()
может сделать из этой строки обычную функцию, которую вы можете вызывать с пользовательской привязкой this
через Function.prototype.apply()
.
Опираясь на ваш пример:
function ES5() {
this.foo = 'foo';
}
class ES6 {
constructor() {
this.bar = 'bar';
}
}
const b = new ES5();
const constructorBody = ES6.toString().match( /(?<=constructor\(\) ){[^}]*}/ )[0]
const ordinaryFunction = Function( constructorBody )
ordinaryFunction.apply( b ); // No TypeError
console.log( b.foo + b.bar ); // foobar !
Обратите внимание, что этот фрагмент использует чрезвычайно упрощенное регулярное выражение для демонстрационных целей.Чтобы сделать вещи более надежными, вам необходимо учитывать вложенные фигурные скобки и фигурные скобки в строках и комментариях.Вам также необходимо извлечь аргументы конструктора, если они необходимы.
(Согласно разделу 19.2.3.5 спецификаций, вы можете полагаться на достаточно последовательный вывод Function.prototype.toString()
, чтобы этот подход работал во всех реализациях.)
Потенциальные проблемы
new.target
будет установлен на undefined
при выполнении обычной функции (как всегда в случае с[[Call]]
вызовов), которые могут вызвать проблемы, если конструктор класса использовал его. - Закрытия исходного конструктора класса будут потеряны для новой функции, созданной с помощью
Function()
( MDN ), что может привести к ReferenceErrors
, если конструктор класса полагался на них. - Этот подход приведет к
SyntaxError
при применении к производному классу с использованием super()
, что не является допустимым синтаксисом в обычномфункции.
Заключение
Идеального решения вашей проблемы не существует.Если ваш сценарий использования достаточно прост, вы все равно сможете достичь того, чего хотите.Частичные обходные пути будут сопровождаться пагубными проблемами - действовать осторожно!