TypeScript should assign to `this` before `_super` call in transpiled output for ES5?

丶灬走出姿态 提交于 2019-12-05 19:57:46

Child cannot be born before parent "existence".

In Java and other OOP language super() must be called before current object is instantiated.

This is logical, because child cannot be born before parent.

TypeScript 2 now can have statements before super, if they are not using to this.

This was the one part of the answer why this cannot be used before supper.


Child override methods that are used in the constructor should purely exist on "parent" resources.

Next part that questions touch, is that parent object actually calls an override by its children assemble in the same time when this child is not instantiated at all.

It seems weird because children are not instantiated, but parent constructor calls the children method... And seems unnatural as well like unborn child says "dad".

See similar post about this issue.

But this is a wrong way to think like this. Overrides from children that will be used in the constructor, purely exist to change how your child will be instantiated.

Method override that used in parent constructor must tell how your instance should be crafted. From those resources that are available for a parent, but not from the resource that your non-existing instance has.


Duck typing prototypes and inheritance...

Inheritance in prototypes usually achieved by compositing a new prototype with extend functionality like this.

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 __());
};

From this perspective, there are no "children" and "parents" as such, but there are "sets", sort of. Set can be extended by another set only when it is already existing. This brings us to:

Top-down and bottom-up design.

Prototypes and duck typing work in bottom-up design. OOP in top down design.


How to come around this weird situation in this case?

Just don't! Use the power of OOP ideas, by learning them and implementing! Here how to succeed:

  • Composition over inheritance, rethink the code design. Split base class to interface and a class, instance of which you can pass to the constructor of "child" class, and compose a desired instance by implementing declared an interface.
  • Use static, but be aware that this change will be the same for all instances for your object.

    This is ok if you only use this for dependency injections

  • Smart override.

    Do not use extra resources from sibling ("child") instance, and create an own extra method that will be called from the constructor.

    The example below (Please note, this does not violate the LSP because only __assembled is set only once in a constructor):

    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();
    

Here is the transpiled ES5 result:

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();
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!