Is there an alias for 'this' in TypeScript?
Solution 1
So as stated there is no TypeScript mechanism for ensuring a method is always bound to its this
pointer (and this isn't just a jQuery issue.) That doesn't mean there isn't a reasonably straightforward way to address this issue. What you need is to generate a proxy for your method that restores the this
pointer before calling your callback. You then need to wrap your callback with that proxy before passing it into the event. jQuery has a built in mechanism for this called jQuery.proxy()
. Here's an example of your above code using that method (notice the added $.proxy()
call.)
class Editor {
textarea: JQuery;
constructor(public id: string) {
this.textarea = $(id);
this.textarea.focusin($.proxy(onFocusIn, this));
}
onFocusIn(e: JQueryEventObject) {
var height = this.textarea.css('height'); // <-- This is not good.
}
}
That's a reasonable solution but I've personally found that developers often forget to include the proxy call so I've come up with an alternate TypeScript based solution to this problem. Using, the HasCallbacks
class below all you need do is derive your class from HasCallbacks
and then any methods prefixed with 'cb_'
will have their this
pointer permanently bound. You simply can't call that method with a different this
pointer which in most cases is preferable. Either mechanism works so its just whichever you find easier to use.
class HasCallbacks {
constructor() {
var _this = this, _constructor = (<any>this).constructor;
if (!_constructor.__cb__) {
_constructor.__cb__ = {};
for (var m in this) {
var fn = this[m];
if (typeof fn === 'function' && m.indexOf('cb_') == 0) {
_constructor.__cb__[m] = fn;
}
}
}
for (var m in _constructor.__cb__) {
(function (m, fn) {
_this[m] = function () {
return fn.apply(_this, Array.prototype.slice.call(arguments));
};
})(m, _constructor.__cb__[m]);
}
}
}
class Foo extends HasCallbacks {
private label = 'test';
constructor() {
super();
}
public cb_Bar() {
alert(this.label);
}
}
var x = new Foo();
x.cb_Bar.call({});
Solution 2
The scope of this
is preserved when using the arrow function syntax () => { ... }
- here is an example taken from TypeScript For JavaScript Programmers.
var ScopeExample = {
text: "Text from outer function",
run: function() {
setTimeout( () => {
alert(this.text);
}, 1000);
}
};
Note that this.text
gives you Text from outer function
because the arrow function syntax preserves the "lexical scope".
Solution 3
As covered by some of the other answers, using the arrow syntax to define a function causes references to this
to always refer to the enclosing class.
So to answer your question, here are two simple workarounds.
Reference the method using the arrow syntax
constructor(public id: string) {
this.textarea = $(id);
this.textarea.focusin(e => this.onFocusIn(e));
}
Define the method using the arrow syntax
onFocusIn = (e: JQueryEventObject) => {
var height = this.textarea.css('height');
}
Solution 4
You can bind a member function to its instance in the constructor.
class Editor {
textarea: JQuery;
constructor(public id: string) {
this.textarea = $(id);
this.textarea.focusin(onFocusIn);
this.onFocusIn = this.onFocusIn.bind(this); // <-- Bind to 'this' forever
}
onFocusIn(e: JQueryEventObject) {
var height = this.textarea.css('height'); // <-- This is now fine
}
}
Alternatively, just bind it when you add the handler.
this.textarea.focusin(onFocusIn.bind(this));
Solution 5
Try this
class Editor
{
textarea: JQuery;
constructor(public id: string) {
this.textarea = $(id);
this.textarea.focusin((e)=> { this.onFocusIn(e); });
}
onFocusIn(e: JQueryEventObject) {
var height = this.textarea.css('height'); // <-- This will work
}
}
Related videos on Youtube
Todd
Updated on July 05, 2022Comments
-
Todd almost 2 years
I've attempted to write a class in TypeScript that has a method defined which acts as an event handler callback to a jQuery event.
class Editor { textarea: JQuery; constructor(public id: string) { this.textarea = $(id); this.textarea.focusin(onFocusIn); } onFocusIn(e: JQueryEventObject) { var height = this.textarea.css('height'); // <-- This is not good. } }
Within the onFocusIn event handler, TypeScript sees 'this' as being the 'this' of the class. However, jQuery overrides the this reference and sets it to the DOM object associated with the event.
One alternative is to define a lambda within the constructor as the event handler, in which case TypeScript creates a sort of closure with a hidden _this alias.
class Editor { textarea: JQuery; constructor(public id: string) { this.textarea = $(id); this.textarea.focusin((e) => { var height = this.textarea.css('height'); // <-- This is good. }); } }
My question is, is there another way to access the this reference within the method-based event handler using TypeScript, to overcome this jQuery behavior?
-
Daniel Little about 11 yearsThe answer using () => {} looks good
-
-
Todd over 11 yearsThe only issue with this solution is that the
this
reference within the callback method masks thethis
reference of the underlying object, so there's no way to access the new property via the correctthis
. -
Todd over 11 yearsThe
$.proxy()
call is a great solution for my situation. Having athis
alias would involve complicating TS and, as others have pointed out, would be difficult to do in a backwards-compatible way. -
Steven Ickman over 11 yearsThought I'd add that I know the code for HasCallbacks looks a little scary but all its doing is going through your classes members and pre-wiring them with proxies. If your project is small it's probably best to use $.proxy(). If your project is large, however, the HasCallbacks class will result in less code downloaded to the client (you don't have all those extra $.proxy() calls) and will be less error prone. Execution wise both approaches have about the same performance (HasCallbacks has a one-time enumeration overhead for each class) so it's really your choice.
-
Paul Mendoza over 11 yearsThe context of "this" isn't be modified with your example and so what you showed doesn't really apply.
-
Fenton over 11 years@PaulMendoza actually if you change it to
setTimeout( function () { alert(this.text); }, 1000);
you will getundefined
. -
Paul Mendoza over 11 yearsYou're right. My bad. Somehow the ()=> tells the compiler that this means something different than doing function() { }
-
Richard Rout almost 11 yearsThis is wonderful. Props! What this does behind the scenes is create a
var _this = this
at the top of the method and then references_this
in the anonymous function. -
Sam over 10 yearsI think the your answer can be misleading since it gave me the impression that TypeScript didn't support this, whereas it actually does if you just define your methods using the arrow syntax.
-
Sam over 10 yearsWhile this is a valid approach, I think it's better to use the built-in functionality provided by TypeScript to achieve this.
-
Sheridan Bulger over 10 yearsTodd: That's exactly why you stored the proper "this" in a variable before the new scope (and therefore the new "this") is entered. This is a pretty common javascript problem, not just typescript (@Sam). Not sure why the down-vote but it's all good.
-
Sam over 10 years@SheridanBulger, the down-vote is because this is a TypeScript question and TypeScript already provides its own way to achieve this. I think it's generally a good idea to make use of language features rather than reimplementing them yourself.
-
BenjaminPaul over 9 years@SheridanBulger Downvoted... this is not a TypeScript answer.
-
antoine129 about 9 yearsthis is my favorite method, but unfortunately you need a polyfill for
bind
for older browsers. -
cesaraviles almost 9 yearsThis answer may provide a solution but is not correct since TypeScript does provide a mechanism for maintaining lexical scoping of this. See Sam's answers for 2 options of using arrow functions to solve this.
-
Carrie Kendall about 8 yearsWhile this implementation is sort of ugly, it is a great way to surpass scope hijacking. +1