Почему TypeScript упаковывает класс в IIFE?

Вот класс TypeScript:

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

    public subject: string;

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

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

Он переносится в IIFE, когда TS нацеливается на 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;
}());

Тем не менее, он обычно работает таким же образом, когда он представлен как функция конструктора. Который, конечно, выглядит более JavaScriptish и рукописный :)

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

Использование:

Оба блока кода работают одинаково:

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

Какова выгода или мотивы, чтобы упаковать его в IIFE?

Я сделал наивный тест:

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

Он показал практически одинаковую скорость реализации. Конечно, мы не можем ожидать никакой разницы, потому что IIFE решается только один раз.

Я думал, что, возможно, это из-за закрытия, но IIFE не принимает аргументы. Это не должно быть закрытием.

Ответы

Ответ 1

TypeScript будет передавать аргументы IIFE в тех случаях, когда существует наследование между классами. Например, закрытие ниже используется, когда Greeter расширяет класс BaseGreeter:

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

Это сделано для того, чтобы сохранить поведение нативного класса в подобных случаях, когда кто-то пытается использовать класс Greeter до того, как он определит:

// this is javascript code, not TypeScript

console.log(Greeter.What());

class Greeter {
}

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

При реализации собственного класса это должно вывести ReferenceError: Greeter is not defined.

При переносе и переносе в IIFE результат достаточно близок: TypeError: Cannot read property 'What' of undefined.

Без IIFE, развернутая функция поднимается, и имя Greeter находится в области видимости, прежде чем оно определено, поэтому возникает другая ошибка TypeError: Greeter.What is not a function

Обратите внимание, что IIFE не используется, чтобы скрыть свойства частного экземпляра или класса, потому что это все равно не нужно. При передаче свойства экземпляра назначаются как свойства для this внутри конструктора, а статические свойства назначаются как свойства объекта Greeter - переменные не создаются.