为什么TypeScript要将类打包在IIFE中?

18

这里是一个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;
    }
}
它在TS目标为ES5时被转译为IIFE:

它在TS目标为ES5时被转译为IIFE:

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

然而,当它作为构造函数呈现时,它通常以同样的方式工作。当然,这看起来更像JavaScript和手写代码 :)

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没有带参数。这一定不是一个闭包。


@charlietfl TS实际上并没有使用IIFE来隐藏“private”字段(http://www.typescriptlang.org/play/#src=class%20Foo%20%7B%0A%20%20%20%20public%20name%20%3D%20%22%22%3B%0A%20%20%20%20private%20_name%20%3D%20%22%22%3B%0A%7D)。在JS中,它们都是公共的。 - Aaron Beall
2个回答

14

在类之间存在继承关系时,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));

12

这样做是为了在像这种边缘情况下保留原生类的行为,例如有人在定义类 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对象的属性 - 不创建变量。


var 不会被提升。 - charlietfl
1
是的,另一个答案是正确的——你必须能够引入一个名称来引用基类,因为例如基类可能是一个mixin。IIFE是唯一安全引入名称的方式,这个名称不会与外部作用域中的任何内容冲突。 - artem
我认为两个答案都是对的,只是针对不同的事情。是的,var没有被提升,但是像OP展示的function Greeter本身会被提升,这是我认为artem在这里想表达的观点。同时也赞同TS实际上并没有将其用于私有字段的观点。 - Aaron Beall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接