-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wrong 'this' context in class methods #3927
Comments
We can't possibly do this. A non-exhaustive list of reasons:
|
|
@agrafix you are arguing for Also for the third point... class Foo {
private bar: string = "Bar";
logBar(): void {
console.log("Bar's value is: " + this.bar);
}
}
let x = new Foo();
x.logBar = undefined; Is perfectly valid TypeScript (let alone valid ES6). This is because (I think) TypeScript has to support assigning |
@kitsonk Hm, if that example is valid TypeScript I find that odd... Wouldn't that mean that @RyanCavanaugh While I don't know too much about the ES6 Spec, I found 4.3.31 method stating:
I agree that my first proposal is very hacky, so we came up with another solution together with @skogsbaer : class Foo {
private bar: string = "Bar";
logBar = () => {
console.log("Bar's value is: " + this.bar);
}
} That way the typing of |
So the question really is: what is the semantics of |
Hm, in this case that's sad news for the ES6 standard that the 'this' binding is messed up for method calls... :-( I don't understand the perf difference between class Foo {
private bar: string = "Bar";
logBar(): void {
console.log("Bar's value is: " + this.bar);
}
logBar2 = () => {
console.log("Bar's value is: " + this.bar);
}
logBar3 = function () {
console.log("Bar's value is: " + this.bar);
}.bind(this)
} Wouldn't this be equivalent to something like this: function Foo2() {
this.bar = "Bar";
this.prototype.logBar = function () {
console.log("Bar's value is: " + this.bar);
};
this.prototype.logBar2 = function () {
console.log("Bar's value is: " + this.bar);
}.bind(this);
this.prototype.logBar3 = function () {
console.log("Bar's value is: " + this.bar);
}.bind(this);
} |
That code doesn't even run? It makes zero sense to try to put a |
Sorry, that was a mistake this morning. I now copied the output of the typescript compiler (and removed the console.log and added a simple incr operation) to benchmark the differences: var Foo = (function () {
function Foo() {
var _this = this;
this.bar = "Bar";
this.ctr = 0;
this.logBar2 = function () {
_this.ctr++;
};
this.logBar3 = function () {
this.ctr++;
}.bind(this);
}
Foo.prototype.logBar = function () {
this.ctr++;
};
return Foo;
})(); Benchmark in Google Chrome (Version 42.0.2311.135) with http://benchmarkjs.com/ : var suite = new Benchmark.Suite;
var f = new Foo();
suite.add('logBar method call', function() {
f.logBar();
})
.add('logBar arrow binding', function() {
f.logBar2();
})
.add('logBar .bind(this)', function() {
f.logBar3();
})
// add listeners
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
})
// run async
.run({ 'async': true }); The classic method call is fastest, and the arrow binding is 25% percent slower. The
|
You can! Simply write your method as an arrow function and this will happen. You should also understand that it isn't just a case of a 25% function invocation hit. Let's say you have a class with 30 methods and you allocate 200 instances of it. If all methods are on the prototype, you'll allocate 30 closures. If all methods are put on the instance, you'll allocate 6,000 closures. That's going to have a broad impact - the GC will run slower and more frequently, you'll have more overall memory pressure, and decreased memory locality. |
You can ? And what about this in properties ? Like get/set ? |
@RyanCavanaugh So what's a proposed practice to overcome the issue? |
For this issue, I would suggest binding the method to "this" in the constructor. Some nice info about it here:
|
Here's a solution I've come up to: https://stackoverflow.com/a/46256398/3095779. |
Currently, the type system included in TypeScript does not allow proper typing of the this context. There are attempts to fix that (See #3694 for example), but these will take a while to ripe and until then I would like to suggest a "quick fix" for class methods, because the following pitfall appears often when interacting with web-libraries. Consider the following code:
This code typechecks perfectly:
But does not produce the desired result:
Many libraries rebind the this context for callbacks, so my suggestion would be that the tsc transforms methods passed as arguments by wrapping them in an anonymous function like so:
This should be okay, be cause inside a method the compiler assumes that
this
is of the classes type so there's no way to interact with later bound this objects anyhow.The text was updated successfully, but these errors were encountered: