TypeScript "this" scope problem when called in jquery callback

I am not sure of the best approach to handling the "this" area in TypeScript.

Here is an example of a generic template in code that I convert to TypeScript:

class DemonstrateScopingProblems { private status = "blah"; public run() { alert(this.status); } } var thisTest = new DemonstrateScopingProblems(); // works as expected, displays "blah": thisTest.run(); // doesn't work; this is scoped to be the document so this.status is undefined: $(document).ready(thisTest.run); 

Now I can change the call to ...

 $(document).ready(thisTest.run.bind(thisTest)); 

... which is working. But this is something terrible. This means that in some cases the code may compile and work fine, but if we forget to link the area, it will break.

I need a way to do this inside the class, so when using the class we don’t have to worry about what β€œthis” is attached to.

Any suggestions?

Update

Another approach that works is to use a fat arrow:

 class DemonstrateScopingProblems { private status = "blah"; public run = () => { alert(this.status); } } 

Is this a valid approach?

+104
this typescript
Dec 17 '13 at 6:06
source share
4 answers

You have several options, each of which has its own trade-offs. Unfortunately, there is no obvious better solution, and it will really depend on the application.

Auto class binding
As shown in your question:

 class DemonstrateScopingProblems { private status = "blah"; public run = () => { alert(this.status); } } 
  • Good / bad: this creates an extra method closure per instance of your class. If this method is usually used only in regular method calls, this is an excess. However, if he used a lot in callback positions, it is more efficient for the class instance to capture the this context instead of each call site, creating a new closure on the call.
  • Good: it’s impossible for external callers to forget to process the this context
  • Good: Types in TypeScript
  • Good: no extra work if the function has parameters
  • Bad: Derived classes cannot call base class methods written this way using super.
  • Bad: the exact semantics of the methods are β€œpre-attached” and which do not create an additional non-financial contract between your class and its consumers.

Function.bind
Also as shown:

 $(document).ready(thisTest.run.bind(thisTest)); 
  • Good / bad: The opposite memory / performance exchange compared to the first method.
  • Good: no extra work if the function has parameters
  • Bad: in TypeScript, it currently does not have type security
  • Bad: only available in ECMAScript 5, if that matters to you
  • Bad: you need to enter instance name twice

Thick arrow
In TypeScript (some dummy parameters are shown here for explanatory reasons):

 $(document).ready((n, m) => thisTest.run(n, m)); 
  • Good / bad: The opposite memory / performance exchange compared to the first method.
  • Good: in TypeScript, it has 100% security
  • Good: works in ECMAScript 3
  • Good: you only need to enter the instance name once
  • Bad: you have to enter the parameters twice
  • Bad: does not work with variable parameters
+160
Dec 17 '13 at 7:07
source share

Another solution, which requires some initial setup, but pays off with its invincible light, literally in a word, uses Decorators methods for JIT-bind methods through getters.

I created a repository on GitHub to demonstrate the implementation of this idea (it's a bit long to fit in with the answer from its 40 line of code, including comments), which you would use the same way:

 class DemonstrateScopingProblems { private status = "blah"; @bound public run() { alert(this.status); } } 

I have never seen this anywhere, but it works flawlessly. In addition, there is no noticeable flaw in this approach: the implementation of this decorator, including some type checking for safety at runtime, is trivial and simple, and when the method is invoked with zero overhead, the value is almost zero.

The essential part is the definition of the following getter in the prototype of the class, which is executed immediately before the first call:

 get: function () { // Create bound override on object instance. This will hide the original method on the prototype, and instead yield a bound version from the // instance itself. The original method will no longer be accessible. Inside a getter, 'this' will refer to the instance. var instance = this; Object.defineProperty(instance, propKey.toString(), { value: function () { // This is effectively a lightweight bind() that skips many (here unnecessary) checks found in native implementations. return originalMethod.apply(instance, arguments); } }); // The first invocation (per instance) will return the bound method from here. Subsequent calls will never reach this point, due to the way // JavaScript runtimes look up properties on objects; the bound method, defined on the instance, will effectively hide it. return instance[propKey]; } 

Full source




The idea can also be taken one step further by doing it in the class decorator instead, iterating over the methods and defining the property descriptor above for each of them in a single pass.

+15
Oct 06 '16 at 21:11
source share

Necromancing.

There is an obvious simple solution that does not require arrow functions (arrow functions are 30% slower), or JIT methods through getters.
This solution is to bind this context in the constructor.

 class DemonstrateScopingProblems { constructor() { this.run = this.run.bind(this); } private status = "blah"; public run() { alert(this.status); } } 
+14
Sep 18 '17 at 15:09 on
source share

In your code, did you try to just change the last line as follows?

 $(document).ready(() => thisTest.run()); 
+2
Mar 23 '17 at 20:54 on
source share



All Articles