Is there an alias for 'this' in TypeScript?

30,673

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
    }

}
Share:
30,673

Related videos on Youtube

Todd
Author by

Todd

Updated on July 05, 2022

Comments

  • Todd
    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
      Daniel Little about 11 years
      The answer using () => {} looks good
  • Todd
    Todd over 11 years
    The only issue with this solution is that the this reference within the callback method masks the this reference of the underlying object, so there's no way to access the new property via the correct this.
  • Todd
    Todd over 11 years
    The $.proxy() call is a great solution for my situation. Having a this alias would involve complicating TS and, as others have pointed out, would be difficult to do in a backwards-compatible way.
  • Steven Ickman
    Steven Ickman over 11 years
    Thought 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
    Paul Mendoza over 11 years
    The context of "this" isn't be modified with your example and so what you showed doesn't really apply.
  • Fenton
    Fenton over 11 years
    @PaulMendoza actually if you change it to setTimeout( function () { alert(this.text); }, 1000); you will get undefined.
  • Paul Mendoza
    Paul Mendoza over 11 years
    You're right. My bad. Somehow the ()=> tells the compiler that this means something different than doing function() { }
  • Richard Rout
    Richard Rout almost 11 years
    This 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
    Sam over 10 years
    I 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
    Sam over 10 years
    While this is a valid approach, I think it's better to use the built-in functionality provided by TypeScript to achieve this.
  • Sheridan Bulger
    Sheridan Bulger over 10 years
    Todd: 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
    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
    BenjaminPaul over 9 years
    @SheridanBulger Downvoted... this is not a TypeScript answer.
  • antoine129
    antoine129 about 9 years
    this is my favorite method, but unfortunately you need a polyfill for bind for older browsers.
  • cesaraviles
    cesaraviles almost 9 years
    This 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
    Carrie Kendall about 8 years
    While this implementation is sort of ugly, it is a great way to surpass scope hijacking. +1