Why does TypeScript pack a class in an IIFE?

若如初见. 提交于 2019-12-21 03:39:59

问题


Here is a TypeScript class:

class Greeter {
    public static what(): string {
        return "Greater";
    }

    public subject: string;

    constructor(subject: string) {
        this.subject = subject;
    }

    public greet(): string {
        return "Hello, " + this.subject;
    }
}

It is transpiled to IIFE when TS targets ES5:

var Greeter = /** @class */ (function () {
    function Greeter(subject) {
        this.subject = subject;
    }
    Greeter.what = function () {
        return "Greater";
    };
    Greeter.prototype.greet = function () {
        return "Hello, " + this.subject;
    };
    return Greeter;
}());

However, it generally works in the same way when it is presented as a constructor function. Which, of course, looks more JavaScriptish and handwritten :)

function Greeter(subject) {
    this.subject = subject;
}
Greeter.what = function () {
    return "Greater";
};
Greeter.prototype.greet = function () {
    return "Hello, " + this.subject;
};

Usage:

Both blocks of code work in the same way:

Greater.what();  // -> "Greater"
var greater = new Greater("World!");
greater.greet(); // -> "Hello, World!

What is the benefit or motives to pack it in IIFE?

I made a naive benchmark:

console.time("Greeter");
for(let i = 0; i < 100000000; i++) {
    new Greeter("world" + i);
}
console.timeEnd("Greeter");

It showed virtually the same instantiation speed. Of course, we cannot expect any difference, because the IIFE is resolved only once.

I was thinking that maybe it is because of closure, but the IIFE doesn't take arguments. It must not be a closure.


回答1:


TypeScript will pass arguments to the IIFE in cases where there is inheritance between classes. For example, the closure below is used when Greeter extends a BaseGreeter class:

var Greeter = /** @class */ (function (_super) {
    // __extends is added by the TS transpiler to simulate inheritance
    __extends(Greeter, _super);
    function Greeter(subject) {
        var _this = _super.call(this) || this;
        _this.subject = subject;
        return _this;
    }
    Greeter.What = function () {
        return "Greater";
    };
    Greeter.prototype.greet = function () {
        return "Hello, " + this.subject;
    };
    return Greeter;
}(BaseGreeter));



回答2:


It's done to preserve native class behavior in edge cases like this, where someone tries to use class Greeter before it's defined:

// this is javascript code, not TypeScript

console.log(Greeter.What());

class Greeter {
}

Greeter.What = function What() {
    return "Greater";
}

With native class implementation, this should print ReferenceError: Greeter is not defined.

When transpiled and wrapped in IIFE, the result is close enough: TypeError: Cannot read property 'What' of undefined.

Without IIFE, unwrapped function is hoisted and name Greeter is in scope before it's defined, so the different error is produced: TypeError: Greeter.What is not a function

Note that IIFE is not used to hide private instance or class properties because it's not necessary anyway. When transpiled, instance properties are assigned as properties for this inside the constructor, and static properties are assigned as properties of Greeter object - no variables are created.



来源:https://stackoverflow.com/questions/56086411/why-does-typescript-pack-a-class-in-an-iife

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