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
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() });
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With