Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why compiler doesn't translate "this" link into context-agnostic variable?

Suppose I have a class (very simple scenario)

class Student
{
    name = "John";

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

It's compiled by TypeScript compiler to:

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

Now if I create an object and call a method, everything works fine.

var student = new Student();

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

But if I invoke that method from callback, it breaks (this is referencing a Window as expected)

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

I'm aware of the difference between this in JavaScript and C# or Java. I'm also aware, that TypeScript tries to address this difference. For example this code:

class Student
{
    name = "John";

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

Would have been compiled to

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

Why isn't the compiler creates something like _this variable in the first scenario for class members? I would expect to see something along next code (not a real output and this code is not correct either, just to show my intention)

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

I've used the TypeScript v0.9.7 compiler

like image 585
Ilya Ivanov Avatar asked Mar 25 '14 13:03

Ilya Ivanov


2 Answers

You might want to change the sayHello function like below to make it generate to code you want. Notice the sayHello = () => { } This will still work with multiple students which is not the case with your example.

class Student
{
    name = "John";

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

It will generate code like this:

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

Another possibility is to change the call to setTimeout like this

setTimeout(() => { student.sayHello() });
like image 102
Dick van den Brink Avatar answered Nov 15 '22 01:11

Dick van den Brink


The only thing that the compiler could do would be to make sure each constructed object had a bound copy of the prototype functions. That would involve a very significant semantic change, so it can't really do that.

The translated code returns a function that has access to a closure, it's true. However, in your suggested alternative, there's only one _this that would be shared by all instances created by the constructor. The closure is in that function that is called to create the "Student" constructor; that function only runs once, when the constructor is made, and then never again. Thus each call to new Student() would update that single, shared variable _this. (In the example, the way that would cause a problem would be for the "name" property to change on a Student instance. If they all are named "John" it doesn't matter :)

The fundamental issue is that in JavaScript, there is no intrinsic relationship between a function and any object. When you call

setTimeout(student.sayHello, 100);

the first parameter expression evaluates to a plain reference to that "sayHello" function. The fact that the reference came from the object is lost. I suppose another alternative for Typescript would be to catch those sorts of expressions and create a bound function at that point. That is, the class code itself would remain the same, but the setTimeout() call would be translated as

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

What sort of ramifications that would have on everything I can't say. I also don't know how hard it would be for the compiler to know that it should do that transformation; there might be times at which it doesn't make sense.

like image 38
Pointy Avatar answered Nov 14 '22 23:11

Pointy