TypeScript должен назначать `this` перед вызовом` _super` в переданном выходе для ES5?

Я использую инъекцию зависимостей для всех моих дочерних классов, которые расширяют абстрактный класс.

Проблема, заключающаяся в том, что в классе abstract constructor я запускаю метод, который я планировал переопределить в своих дочерних элементах, если это необходимо.

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

Вот пример кода:

abstract class Base {

    constructor(view: string) {
        this._assemble();
    }

    protected _assemble(): void {
        console.log("abstract assembling for all base classes");
    }

}

class Example extends Base {

    constructor(view: string, private helper: Function) {
        super(view);
        console.log(this.helper);
    }

    public tryMe(): void {
        this._assemble();
    }

    protected _assemble(): void {
        super._assemble();
        // at first run this.helper will be undefined!
        console.log("example assembling", this.helper);
    }

}

let e = new Example("hoho", function () { return; })
console.log("So now i will try to reassemble...");
e.tryMe();

Итак, ядро ​​проблемы состоит в том, что typescript преобразует класс Example в код следующим образом:

function Example(view, helper) {
    _super.call(this, view);
    this.helper = helper;
    console.log(this.helper);
}

Вместо этого:

function Example(view, helper) {
    this.helper = helper;
    _super.call(this, view);
    console.log(this.helper);
}

Как вы видите, если я помещаю this.helper до _super в JavaScript, this.helper будет всегда отображаться в _assemble. Даже если super вызовет функцию _assemble.

Но по умолчанию это назначается после вызова _super. Поэтому, если класс super вызовет сборку. Он не будет отображаться при переопределении метода _assemble в примере в первый раз.

Итак, мой вопрос...

Это ошибка?

или

Что я не знаю?

Теперь я исправил свою проблему, просто удалив _assemble из класса super и всегда вызываю ее из дочернего. Но это просто неправильно.

Nota Bene: Вот скомпилированный код JavaScript и демонстрационный код JavaScript:

TypeScript обычный вывод:

var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Base = (function () {
    function Base(view) {
        this._assemble();
    }
    Base.prototype._assemble = function () {
        document.write("<p>abstract assembling for all base classes</p>");
    };
    return Base;
}());
var Example = (function (_super) {
    __extends(Example, _super);
    function Example(view, helper) {
        _super.call(this, view);
        this.helper = helper;
        console.log(this.helper);
    }
    Example.prototype.tryMe = function () {
        this._assemble();
    };
    Example.prototype._assemble = function () {
        _super.prototype._assemble.call(this);
        // at first run this.helper will be undefined!
        document.write("<p>example assembling <b/>" + (this.helper) + "</b></p>");
    };
    return Example;
}(Base));
var e = new Example("test", function () { return "needle"; });
document.write("<p><i>So now i will try to reassemble...</i></p>");
e.tryMe();

Ответ 1

Ребенок не может родиться до родительского "существования".

В Java и других языках OOP super() должен быть вызван до. Создан текущий объект.

Это логично, потому что child cannot be born before parent.

TypeScript 2 теперь могут иметь инструкции перед super, если они не используются для this.

Это была одна из частей ответа, почему this нельзя использовать перед ужином.


Методы переопределения детей, которые используются в конструкторе, должны существовать исключительно в "родительских" ресурсах.

Следующая часть, на которую накладываются вопросы, заключается в том, что объект parent фактически вызывает переопределение своих дочерних элементов assemble в то же время, когда этот ребенок не создается вообще.

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

См. подобное сообщение об этой проблеме.

Но это неправильный способ думать так. Переопределения от детей, которые будут использоваться в конструкторе, существуют только для изменения того, как будет создан ваш ребенок.

Переопределение метода, используемое в родительском конструкторе, должно указывать, как должен обрабатываться ваш экземпляр. Из тех ресурсов, которые доступны для родителя, но не из ресурса, который имеет ваш несуществующий экземпляр.


Утиные типизирующие прототипы и наследование...

Наследование в прототипах обычно достигается путем компоновки нового прототипа с такими функциональными возможностями extend.

var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};

С этой точки зрения нет "детей" и "родителей" как таковых, но есть "множества", вроде. Набор может быть расширен другим набором только тогда, когда он уже существует. Это приводит нас к:

Дизайн сверху вниз и снизу вверх.

Прототипы и утиная печать работают в снизу вверх. ООП в дизайне сверху вниз.


Как обойти эту странную ситуацию в этом случае?

Просто не надо! Используйте силу идей ООП, изучая их и внедряя! Здесь, как добиться успеха:

  • Состав над наследованием, переосмыслите дизайн кода. Разделите базовый класс на интерфейс и класс, экземпляр которого вы можете передать конструктору "дочернего" класса, и составить желаемый экземпляр, выполнив объявленный интерфейс.
  • Используйте static, но имейте в виду, что это изменение будет одинаковым для всех экземпляров вашего объекта.

    Это нормально, если вы используете это только для инъекций зависимостей

  • Умное переопределение.

    Не используйте дополнительные ресурсы из экземпляра sibling ( "child" ) и создайте собственный дополнительный метод, который будет вызываться из конструктора.

    Пример ниже (обратите внимание, что это не нарушает LSP, потому что только __assembled устанавливается только один раз в конструкторе):

    abstract class Base {
    
        constructor(view: string) {
            this._assemble();
        }
    
        protected _assemble(): void {
            console.log("abstract assembling for all base classes");
        }
    
    }
    
    class Example extends Base {
    
        private __assembled: boolean = false;
    
        constructor(view: string, private helper: Function) {
            super(view);
            this._assemble_helper();
            this.__assembled = true;
        }
    
        public tryMe(): void {
            this._assemble();
        }
    
        protected _assemble(): void {
            super._assemble();
            // removed from here all extra resources
            // but run them when u need to assemble them again.
            if (this.__assembled) {
                this._assemble_helper();
            }
        }
    
        protected _assemble_helper(): void {
            // at first run this.helper will be undefined!
            console.log("example assembling", this.helper);
        }
    
    }
    
    let e = new Example("hoho", function () { return; })
    console.log("So now i will try to reassemble...");
    e.tryMe();
    

Вот результат с препарированным ES5:

var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Base = (function () {
    function Base(view) {
        this._assemble();
    }
    Base.prototype._assemble = function () {
        console.log("abstract assembling for all base classes");
    };
    return Base;
}());
var Example = (function (_super) {
    __extends(Example, _super);
    function Example(view, helper) {
        var _this = _super.call(this, view) || this;
        _this.helper = helper;
        _this.__assembled = false;
        _this._assemble_helper();
        _this.__assembled = true;
        return _this;
    }
    Example.prototype.tryMe = function () {
        this._assemble();
    };
    Example.prototype._assemble = function () {
        _super.prototype._assemble.call(this);
        // removed from here all extra resources
        // but run them when u need to assemble them again.
        if (this.__assembled) {
            this._assemble_helper();
        }
    };
    Example.prototype._assemble_helper = function () {
        // at first run this.helper will be undefined!
        console.log("example assembling", this.helper);
    };
    return Example;
}(Base));
var e = new Example("hoho", function () { return; });
console.log("So now i will try to reassemble...");
e.tryMe();