Как изменить конструктор класса ES6

Я пытаюсь перезагрузить горячий код с помощью классов ES6. Мне нужно иметь возможность изменять конструктор классов, не заменяя класс новым (потому что другие люди могут иметь на него ссылку).

Однако я обнаружил, что похоже, что объект класса имеет некоторую внутреннюю ссылку на конструктор, с которым он был изначально определен; создание экземпляра класса с new не выполняет поиск ни constructor, ни prototype.constructor в классе.

Пример:

class OldC { constructor() { console.log("old"); } }
class NewC { constructor() { console.log("new"); } }

OldC.prototype.constructor = NewC.prototype.constructor;
OldC.constructor = NewC.constructor;
new OldC();

---> "старый"

(Обновление всех других методов работает нормально; это просто конструктор, с которым у меня проблемы.)

Думая, что конструктор может быть найден через [[prototype]], я также добавил это:

Object.setPrototypeOf(OldC, Object.getPrototypeOf(NewC));
Object.setPrototypeOf(OldC.prototype, Object.getPrototypeOf(NewC.prototype));

Это тоже не помогает (и я бы не ожидал, что это произойдет, учитывая, что подклассы не происходят).

После всего этого проверка OldC показывает, что свойства прототипа в точности такие, как я ожидал, а OldC.prototype.constructor является новым. Но все же создание экземпляра OldC вызывает исходный конструктор.

Что происходит, и как это исправить?


person David Given    schedule 18.03.2019    source источник
comment
Что такое OldObject в вашем примере? создание экземпляра класса с помощью new не выполняет поиск ни constructor, ни prototype.constructor в классе. Нет, потому что вы используете new для объекта функции, а вызывается сам объект функции.   -  person Felix Kling    schedule 19.03.2019
comment
Извините, опечатка. Фиксированный.   -  person David Given    schedule 19.03.2019
comment
Мне нужно иметь возможность изменять конструктор классов, не заменяя класс новым (потому что у других людей могут быть ссылки на него). Я думаю, что единственный способ - иметь сам конструктор делегировать другую функцию, которую вы можете заменить.   -  person Felix Kling    schedule 19.03.2019
comment
Вопрос сводится к следующему: могу ли я изменить функцию. См. этот ответ   -  person trincot    schedule 19.03.2019
comment
В JS класс - это не отдельная вещь. Это просто синтаксический сахар для создания функции-конструктора. Итак, .constructor обоих классов Function.   -  person ziggy wiggy    schedule 19.03.2019
comment
Однако классы ES6 не являются строго функциями; их нельзя назвать, для начала, только построенными ...   -  person David Given    schedule 19.03.2019
comment
@DavidGiven: Да, они все еще функционируют. Поскольку функции ES2015 могут быть либо вызываемыми, либо конструируемыми, либо и тем, и другим.   -  person Felix Kling    schedule 19.03.2019


Ответы (1)


Тем не менее, создание экземпляра OldC вызывает исходный конструктор.

Да, потому что OldC сама является функцией-конструктором. Вам нужно перезаписать переменную OldC, чтобы изменить то, что делает new OldC.

Измените конструктор классов, не заменяя класс новым (потому что у других людей может быть ссылка на него).

Как отметил @trincot в комментариях, вы не можете изменять код функции. Вы должны заменить конструктор на новый, обходного пути нет.

Однако вы можете сохранить объект-прототип (который является изменяемым), поскольку на него будет ссылаться большинство других вещей (особенно старые экземпляры).

NewC.prototype = OldC.prototype;
NewC.prototype.constructor = NewC;
OldC = NewC;

Людям, которые взяли ссылку на старый конструктор, который теперь нельзя изменить, ничем не поможем. Лучше всего передавать не сам класс, а только фабрику, поведение которой ваш код обновления знает, что нужно изменить.

person Bergi    schedule 18.03.2019
comment
К сожалению, людям, которые ссылаются на старый класс, нужно помочь, потому что это неоспоримое требование для этого проекта. Замена его фабрикой также вряд ли сработает, потому что class Subclass extends superclassFactory() будет принимать неявную ссылку на class Superclass, а конструкторы суперкласса также не просматриваются через прототип. Возможно, удастся сделать что-то действительно грубое, расширив экземпляр Proxy; Мне нужно поэкспериментировать. - person David Given; 19.03.2019
comment
@DavidGiven Я не уверен, как вы выполняете перезагрузку горячего кода, но, чтобы помочь людям, взявшим ссылку на старый конструктор, вам, вероятно, понадобится доступ отладчика к среде выполнения, чтобы идентифицировать и изменять все эти ссылки. Если вы хотите сделать это с помощью кода изнутри среды, вы ничего не можете сделать, кроме как использовать constructor(...args) { this.init(...args); } в исходном коде, а затем заменить метод инициализации. - person Bergi; 19.03.2019
comment
На самом деле я использую TypeScript, поэтому я мог бы установить специальный преобразователь, чтобы сделать именно это. Вероятно, это не так грубо, как злоупотребление прокси. Изменить: Кроме этого, я бы потерял все полезные расширения конструктора TypeScript ... - person David Given; 19.03.2019
comment
@DavidGiven А, если вы можете интегрировать это в свою цепочку сборки, то это идеальное решение. Я бы рекомендовал не называть метод init, а скорее как Symbol.for("hotreload-actualconstructor") :-) Или, может быть, вообще не использовать метод, а просто вызвать локальную переменную, к которой ваш перезагрузчик может получить доступ и изменить. - person Bergi; 19.03.2019
comment
Я выполнил эту работу. По сути, я полностью отказался от структуры классов ES6, вернувшись к старомодной модели JS, когда класс является конструктором функции с цепочкой прототипов; конструктор просто вызывает __constructor для нового объекта. Поскольку это обычный метод, я могу его обновить. Затем есть куча мерзких преобразователей TypeScript, которые переписывают объявления классов TypeScript в эту модель. На данный момент он проходит все мои тесты, что меня очень удивляет. - person David Given; 21.03.2019