Is there an alias for 'this' in TypeScript?

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?

31371 次浏览

You could store your reference to this in another variable.. self perhaps, and access the reference that way. I don't use typescript, but that's a method that's been successful for me with vanilla javascript in the past.

TypeScript doesn't provide any extra way (beyond the regular JavaScript means) to get back to the 'real' this reference other than this remapping convenience provided in the fat arrow lambda syntax (which is allowable from a back-compat perspective since no existing JS code could be using a => expression).

You could post a suggestion to the CodePlex site, but from a language design perspective there's probably not much that can happen here, since any sane keyword the compiler could provide might already be in use by extant JavaScript code.

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

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".

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
}


}

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

Steven Ickman's solution is handy, but incomplete. Danny Becket and Sam's answers are shorter and more manual, and fail in the same general case of having a callback that needs both dynamic and lexically scoped "this" at the same time. Skip to my code if my explanation below is TL;DR...

I need to preserve "this" for dynamic scoping for use with library callbacks, and I need to have a "this" with lexical scoping to the class instance. I argue that it is most elegant to pass the instance into a callback generator, effectively letting the parameter closure over the class instance. The compiler tells you if you missed doing so. I use a convention of calling the lexically scoped parameter "outerThis", but "self" or another name might be better.

The use of the "this" keyword is stolen from the OO world, and when TypeScript adopted it (from ECMAScript 6 specs I presume), they conflated a lexically scoped concept and a dynamically scoped concept, whenever a method is called by a different entity. I'm a little miffed at this; I would prefer a "self" keyword in TypeScript so that I can hand the lexically scoped object instance off of it. Alternately, JS could be redefined to require an explicit first-position "caller" parameter when it is needed (and thus break all web pages in one fell swoop).

Here's my solution (excised from a large class). Take a gander in particular at the way the methods are called, and the body of "dragmoveLambda" in particular:

export class OntologyMappingOverview {


initGraph(){
...
// Using D3, have to provide a container of mouse-drag behavior functions
// to a force layout graph
this.nodeDragBehavior = d3.behavior.drag()
.on("dragstart", this.dragstartLambda(this))
.on("drag", this.dragmoveLambda(this))
.on("dragend", this.dragendLambda(this));


...
}


dragmoveLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void} {
console.log("redefine this for dragmove");


return function(d, i){
console.log("dragmove");
d.px += d3.event.dx;
d.py += d3.event.dy;
d.x += d3.event.dx;
d.y += d3.event.dy;


// Referring to "this" in dynamic scoping context
d3.select(this).attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });


outerThis.vis.selectAll("line")
.filter(function(e, i){ return e.source == d || e.target == d; })
.attr("x1", function(e) { return e.source.x; })
.attr("y1", function(e) { return e.source.y; })
.attr("x2", function(e) { return e.target.x; })
.attr("y2", function(e) { return e.target.y; });


}
}


dragging: boolean  =false;
// *Call* these callback Lambda methods rather than passing directly to the callback caller.
dragstartLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void} {
console.log("redefine this for dragstart");


return function(d, i) {
console.log("dragstart");
outerThis.dragging = true;


outerThis.forceLayout.stop();
}
}


dragendLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void}  {
console.log("redefine this for dragend");


return function(d, i) {
console.log("dragend");
outerThis.dragging = false;
d.fixed = true;
}
}


}

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

I have faced a similar problem. I think you can use .each() in many cases to keep this as a different value for later events.

The JavaScript way:

$(':input').on('focus', function() {
$(this).css('background-color', 'green');
}).on('blur', function() {
$(this).css('background-color', 'transparent');
});

The TypeScript way:

$(':input').each((i, input) => {
var $input = $(input);
$input.on('focus', () => {
$input.css('background-color', 'green');
}).on('blur', () => {
$input.css('background-color', 'transparent');
});
});

I hope this helps someone.

You can use js eval function: var realThis = eval('this');

Check out this blog post http://lumpofcode.blogspot.com/2012/10/typescript-dart-google-web-toolkit-and.html, it has a detailed discussion of techniques for organizing calls within and across TypeScript classes.

There is much simpler solution than all the above answers. Basically we fall back to JavaScript by using key word function instead of using '=>' construct which will translate the 'this' into class 'this'

class Editor {
textarea: JQuery;


constructor(public id: string) {
var self = this;                      // <-- This is save the reference
this.textarea = $(id);
this.textarea.focusin(function() {   // <-- using  javascript function semantics here
self.onFocusIn(this);          //  <-- 'this' is as same as in javascript
});
}


onFocusIn(jqueryObject : JQuery) {
var height = jqueryObject.css('height');
}
}