为什么编译器没有翻译"这个"链接到上下文无关的变量?

时间:2014-03-25 13:11:43

标签: javascript typescript

假设我有一个班级(非常简单的场景)

class Student
{
    name = "John";

    sayHello()
    {
        console.log("Hi, I'm " + this.name);
    }
}

它由TypeScript编译器编译为:

var Student = (function () {
    function Student() {
        this.name = "John";
    }
    Student.prototype.sayHello = function () {
        console.log("Hi, I'm " + this.name); //here is the problem. Accessing name via this
    };
    return Student;
})();

现在,如果我创建一个对象并调用一个方法,那么一切正常。

var student = new Student();

student.sayHello(); //prints Hi, I'm John

但是如果我从回调中调用该方法,它会中断(this正在按预期引用一个Window)

setTimeout(student.sayHello); //prints Hi, I'm 

我知道JavaScript和C#或Java中this之间的区别。我也知道,TypeScript试图解决这个差异。例如这段代码:

class Student
{
    name = "John";

    sayHelloTo(other)
    {
        other(() => this.name);
    }
}

本来会被编译为

var Student = (function () {
    function Student() {
        this.name = "John";
    }
    Student.prototype.sayHelloTo = function (other) {
        //note, the compiler solves the problem by capturing this into local variable
        var _this = this; 
        other(function () {
            return _this.name;
        });
    };
    return Student;
})();

为什么编译器在类成员的第一个场景中不会创建类似_this变量的东西?我希望在下一个代码中看到一些东西(不是真正的输出和这个代码)是不正确,只是为了显示我的意图)

var Student = (function () {
    var _this;
    function Student() {
        _this = this; //solves the problem of setTimeout(student.sayHello)
        _this.name = "John";
    }
    Student.prototype.sayHello = function () {
        console.log("Hi, I'm " + _this.name);
    };
    return Student;
})();

我使用了 TypeScript v0.9.7 编译器

2 个答案:

答案 0 :(得分:1)

编译器唯一能做的就是确保每个构造对象都有原型函数的绑定副本。这将涉及非常重要的语义变化,因此它无法真正做到这一点。

翻译后的代码返回一个可以访问闭包的函数,它是真的。但是,在您建议的替代方案中,只有一个_this将由构造函数创建的所有实例共享。闭包在该函数中,该函数被称为创建"学生"构造函数;该函数只在构造函数构建时运行一次,然后再也不运行。因此,每次调用new Student()都会更新该单个共享变量_this。 (在这个例子中,导致问题的方式是" name"属性在Student实例上更改。如果它们都被命名为" John"它没有'重要的是:)

基本问题是在JavaScript中,函数和任何对象之间没有内在关系。当你打电话

setTimeout(student.sayHello, 100);

第一个参数表达式求值为对该" sayHello"的简单引用。功能。引用来自对象的事实将丢失。我认为Typescript的另一个替代方案是捕获那些类型的表达式并在那时创建一个绑定函数。也就是说,类代码本身将保持不变,但setTimeout()调用将被翻译为

setTimeout(student.sayHello.bind(student), 100);

对我能说的一切都会产生什么样的后果。我也不知道编译器知道它应该进行那种转换是多么困难;可能有时候它没有意义。

答案 1 :(得分:1)

您可能希望更改sayHello函数,如下所示,使其生成您想要的代码。注意sayHello =()=> {} 这仍然适用于多个学生,而不是你的例子。

class Student
{
    name = "John";

    sayHello = () =>
    {
        console.log("Hi, I'm " + this.name);
    }
}

它将生成如下代码:

function Student() {
    var _this = this;
    this.name = "John";
    this.sayHello = function () {
        console.log("Hi, I'm " + _this.name);
    };
}

另一种可能性是将调用更改为setTimeout,如此

setTimeout(() => { student.sayHello() });