Typescript: Support specifying type of "this" in function implementations

Created on 24 Jul 2014  ·  58Comments  ·  Source: microsoft/TypeScript

Migrated issue from codeplex: https://typescript.codeplex.com/workitem/507

Currently typescript will type the "this" pointer in function callbacks as "any." Arrow syntax lets us capture "this" from the outer scope, and this can enable proper typing. It would be nice to be able to provide
an optional "ambient this" declaration in function signatures:

(declare this : MyType, first: number, second: string) : any;

The rules would be:

1) the declaration is obviously optional
2) if specified, should be first parameter
3) cannot be used with arrow syntax lambdas (should be compiler error)

I know you can cast as needed to get the intellisense but I'd rather not put this responsibility on the implementer of the function. Ideally it should be part of the definition.

Example with Sammy:

var app = $.sammy("#view", function() {
     // define default route
     this.get("#/", function() { ... } 
});

In the above case, typescript can only type this as any.

Thoughts?

Duplicate

Most helpful comment

Yes. It is sort of covered here: https://www.typescriptlang.org/docs/handbook/functions.html

But taking an example from above:

myMethod(this: MyClass) {
    this.doStuff();
}

All 58 comments

There are _many_ JS libraries out there that depend on using a specifically bound "this" in callbacks.

Note the codeplex issue had 68 votes.

:+1:

:+1:

:+1:

@fdecampredon Have you moved the issue regarding exposing private types? That issue is still important for Facebook React compatibility, right?

Not yet @abergs I'll do it

:+1:

+Needs Proposal (see https://github.com/Microsoft/TypeScript/wiki/Writing-Good-Design-Proposals)

The major design impediment we had here was that it was really unclear what the subtype relationships are for functions with a declared this parameter, and how the various DOM functions that might or might not care about this should act.

Some food for thought:

class MyClass {
    x = 5;
    fn1 = () => { console.log(this.x); }
    fn2() { console.log(this.x); }
}
declare function callme(f: (this: SomeType));

var m = new MyClass();
callme(m.fn1); // Allowed? Not allowed?
callme(m.fn2); // Allowed? Not allowed?
callme(window.focus); // Allowed? Not allowed?

I like "this" for a couple of reasons. Currently there is no way to create practical private values in classes using TypeScript syntax. When the situation requires private members in a class, read "function constructed object", I end up using regular JavaScript syntax and use "this" to create my public members. However as an "any" there is no auto complete of values assigned to "this" in the class, nor can any sub value of an "any" have a type. Being able to define "this" as an interface would mitigate this. Or we could allow private members in classes... but those comments are for a different issue thread.

What if declare this just like we declare "strict mode"; ?

function action() {
  this:SomeClass;
  // ...
}

@redexp I think it's better to have this in the signature, since definition files (.d.ts) don't have function implementations, so how could you generate a definition for your example? Also this gives a problem for functions that have a callback argument:

function callFunction(callback: (this: string) => void) {
    callback.call('foo');
}

How could you write that with this in the function body?

Ok, but as for me it's kinda confusing when this is declared with params. I guess you will say that to put this declaration to square (or some other) brackets after params is also bad idea?

function callFunction(callback: ()[this:string] => void) {
    callback.call('foo');
}

function zooKeeper(cage: AnimalCage)[this:SomeClass];

interface Widget {
    sprock()[this:SomeClass]: void;
}

this must always be the first parameter and this has another color than the other parameters (in Visual Studio and others), that prevents most of the confusion. And, the parameters are the input of a function and the value of this is also a special kind of input, it isn't strange to have all the inputs in one list in my opinion.

Maybe as type parameter?

interface Widget {
    sprock<this:SomeClass>(): void;
}

Would fit because it matches conceptually with how regular type parameters are specified and doesn't need to introduce another syntax pattern. I'm not sure if this can be used as type parameter name at this time but seems reasonable to claim it.

Perhaps we can use a left-side type parameter for specifying this types?

class ButtonNode
{
    public Text: string;
}

class MyClass
{
    private ButtonNode ClickHandler(): void
    {
        console.log(this.Text);
    }
}

@aholmes I think that would be fairly confusing for people coming from many other C-style languages like C#, Java, D, etc.

Even I read that and assume that MyClass.ClickHandler returns a ButtonNode despite working in TypeScript all day.

That's a fair point, though given that the return type follows the signature, maybe this is something to simply get used to.

For what it's worth, I come from the C-style languages, and the ": type" syntax still confuses me, especially when working with JavaScript's object definition syntax.

I like @Bartvds suggestion - fits well I think.

@Bartvds I find it strange to add the this to the generic list, since a generic represents a type and this is a value, just like the arguments. Adding a value to a type list would be more confusing than adding a value to a value list.

How about to separate this and params with |

function show (this: Node | seconds: Number, callback: Function) {}

@redexp That's very concise. That syntax could even be used for classes.

class Something {
    constructor(this:element)
}
class SomethingElse {
    constructor(this:element | otherParam:type)
}

Great idea.

I'm not so keen on having the type of this specified in the parameter list as I think it forces you to do extra mental processing when you're reading the code.

To take @redexp example:

function show (this: Node | seconds: Number, callback: Function) {}

Myself, I find this more readable:

function show<this: Node> (seconds: Number, callback: Function) {}

My reason is that when you look at the parameters you don't have to remember to harvest out the this from the start. I think it improves the code readability generally (which I think is important). That's my 10 cents anyway :smile:

Guys, we extremely need it. All my code is in red because IDE don't know the type of this, which I use a lot. May be let's start some poll where people can decide which option is the best?

@redexp Could you clarify what you mean? 'this' should have the 'any' type. So you shouldn't be seeing any red as the compiler will pretty much consider everything to be valid.

Note: I think that being able to statically type 'this' is important. I just don't understand the case you're bringing up.

Thanks!

Yeah, it's kinda problem only of my editor, it says that methods of this is undeclared, but we all using this a lot and those warnings is really annoying and I'm just asking to make this issue high priority, pleeeease :)

What editor are you using that's doing this? We do want to make some improvements here but if your editor isn't respecting the language semantics then any changes we make won't fix your pain point anyway.

It's not about semantics, it's about bugs, because when this is any for editor any method call of this will be correct. Untyped this in TypeScript is a huge hole for bugs. That's what all those warnings about.

This would be huge for React

I hope I'm not late to the party. I thought about @RyanCavanaugh's post from July and tried to figure out some more concrete semantics. Hopefully others can point out corner cases that I have missed.


Use Cases

It seems as though there are two use cases we're thinking about:

  1. A function wishes to specify the value of its own this binding. Within the body of the function, this is of type ThisType. Anyone who wishes to call the function must somehow bind this to a subtype of ThisType.

function foo(this: ThisType) {}

  1. An interface wants to specify that an unknown function will, whenever invoked, be invoked with this bound to ThisType. The function's implementation must expect its this to be a supertype of ThisType.

declare var registerCallback: (callback: (this: ThisType)=>void)=>void; // an example from https://github.com/borisyankov/DefinitelyTyped/blob/master/jquery/jquery.d.ts#L25 interface JQueryAjaxSettings<Ctx> { // ... context?: Ctx; complete? (this: Ctx, jqXHR: JQueryXHR, textStatus: string): any; // ... }

The this binding is just another argument being passed into a function. It seems to me there is no difference between subtype relations for the this binding and for other arguments being passed into a function.

Arrow Functions and Function.prototype.bind

For arrow functions, this has two types:

  1. From the perspective of a caller (someone calling the arrow function) the arrow's this binding is of type any. This is because the arrow function already has its this value internally bound, so it will ignore any externally-specified this value.
  2. For any code in the body of the arrow function, this is lexically typed. It has the same type as this immediately outside the function.

The same is true when using Function.prototype.bind.

interface Person { firstName?: string; }
function assignName(this: Person) {
    this.firstName = "Arnold";
}
var person: Person = {};
var assignName2 = assignName.bind(person);
// assignName *must* be called with `this` bound to a subtype of Person
// assignName2 can be called with `this` bound to anything.  It will ignore any externally-specified `this` binding.

assignName.call(person);  // allowed; `this` is correctly bound
assignName();             // not allowed; `this` must be externally bound to a subtype of Person
assignName2();            // allowed; assignName2 ignores external `this` binding
assignName.call(window);  // not allowed; window is not a subtype of Person
assignName2.call(window); // allowed; assignName2 ignores external `this` binding
assignName.bind(window);  // not allowed; window is not subtype of Person
assignName2.bind(window); // allowed; assignName2 ignores external `this` binding

Methods

I think methods should automatically have the type of this set to the class.

class Foo {
    counter: number;
    myMethod() { this.counter++; }
    // The above method signature is equivalent to:
    myMethod(this: Foo) { this.counter++; }
}

Attempts to invoke the method without binding this will cause a compiler error.

var foo = new Foo();
foo.myMethod(); // allowed; `this` is being bound correctly.
var m = foo.myMethod;
m(); // not allowed; `this` is not being bound correctly.

If the implementor of Foo wants to support the latter usage, (e.g use myMethod as a callback) they can implement myMethod as an arrow function.

class Foo {
    myMethod = () => { this.counter++; }
}

Here's how I think @RyanCavanaugh's examples should behave:

class MyClass {
    x = 5;
    fn1 = () => { console.log(this.x); }
    fn2() { console.log(this.x); }
}

// Interpretation: whatever function is passed as `f` must be invokable with `this` bound to a subtype of `SomeType`
declare function callme(f: (this: SomeType));

var m = new MyClass();
callme(m.fn1); // Allowed.  fn1 ignores any externally specified `this` binding.
callme(m.fn2); // Not allowed.  fn2 requires `this` to be bound to a subtype of MyClass.
callme(window.focus); // Not allowed.  window.focus requires `this` to be bound to a subtype of window.

// The type definition for window.focus should be something like:
interface Window {
    focus: (this: Window) => void;
}
declare var window: Window;

:+1:

If the interfaces in lib.d.ts are updated to enforce proper this bindings (as in my example interface for window.focus) it could potentially break existing code. For small projects it wouldn't be a big deal, but TypeScript is intended to be used for application-scale development with large codebases.

We also get backwards-incompatibility if we start enforcing proper this binding on methods. For example:

class MyClass {
    myMethod(num: number, str: string): string {
        return num + str + this.myProperty;
    }
    myProperty = 456;
}

var instance = new MyClass();

 // Returns "123hello456".  This makes sense.
var result1 = instance.myMethod(123, "hello");

// Should probably not compile.  In the current TypeScript, it compiles fine and returns "123helloundefined".
var fn = instance.myMethod;
var result2 = fn(123, "hello");

View code in TS Playground

How should TypeScript deal with these backwards-incompatibilities?

I can think of a few solutions off the top of my head but none of them are particularly great.

  1. Add a compiler flag that tells TypeScript to use the old version of lib.d.ts for projects that would otherwise break.
  2. Publish the old this-less lib.d.ts file on TypeScript's website. If a project breaks with the new lib.d.ts, tell them to use --nolib and manually reference the old lib.d.ts file.
  3. Add a compiler flag that disables all this typing (--nothistypes). This could get messy. If it encounters a type definition with function a(this: Window) what should it do? Compiler error? Silently ignore the this type, reverting to old behavior?
  4. Tell projects that would break to use an older version of TypeScript.
  5. Require new code to manually specify the this type for all methods. If a this type is not specified for a method, old (current) behavior is used.

class MyClass { myMethod(this: MyClass) {} // explicitly specified `this` type } var fn = (new MyClass()).myMethod; fn(); // compiler error because of explicitly specified `this` type

  1. Add a --noImplicitThis flag that disables implicitly enforcing the this binding of methods. In other words, a future TypeScript compiler will enforce proper this binding for all methods _unless_ --noImplicitThis is used. If --noImplicitThis is used, the future compiler behaves like the current compiler.

Second @pspeter3's comment: this would be great for the React definitions.

@cspotcode: the proposal looks intuitive. wrt implicit this: it should definitely be default on methods, eventually. Since it's backwards incompatible, perhaps default --noImplicitThis flag on (so feature off) for now, and default it to off in the next (major) version?

Implicit this on methods is a strong feature that deserves to be enabled by default soon, so if the next major rev is too far off, perhaps use the next minor (1.5?) as a beta period (default off) and default on at 1.6?

Lack of implicit method binding in JS is a common source of confusion for newcomers, and TS makes this even less obvious. Foo.prototype.meth = func() gives you a hint that it's not common, but class Foo { meth() { ...} }, well, you have to really understand JS and how TS compiles to JS to see why ((new Foo()).meth)() var f = (new Foo()).meth; f(); won't work. There, I just got confused myself... I would've expected ((new Foo()).meth)() to use caller's this, but it's bound to that fresh Foo instance.

All in all, definitely plus one from me. I'd enable that flag in my builds asap.

Regarding a "noImplicitThis" flag, I propose not adding this and learning from PHP's mistakes. These kinds of flags caused all sorts of problems between different versions of PHP and different libraries using code that "worked" with different flags enabled than what the current system had enabled.

If implicit this is added and is a BC break I propose that this change is made prominent so any developers updating TS are made aware of this.

Any update on what's happening with this?

As far as I know, the Typescript team is still waiting for someone to write a design proposal, which @RyanCavanaugh says in this comment: https://github.com/Microsoft/TypeScript/issues/229#issuecomment-50406084

See also: the guidelines for writing a proposal: https://github.com/Microsoft/TypeScript/wiki/Writing-Good-Design-Proposals

@cspotcode Gotcha. Submitted a proposal over at #1985. It's mostly based on your work so would appreciate the feedback.

Very nice, I will take a look soon. Thanks!

For the time being, can we just use?

var self:MyClassType = this;

@aclave1 yup. But then its a responsibility of the API user to remember to do this _every single time_. Becomes annoying fairly fast.

I will second that this is the workaround I use _and_ that it gets annoying really fast. My suggestion to formalize the ability to use a capture variable (such as if you've converted a JS function to a TS class and want to avoid the risky busy work the compiler currently mandates): https://github.com/Microsoft/TypeScript/issues/1859

@basarat @nycdotnet I agree with you guys.

Consider also the case where you can specify a callback and the context it will be called with. For example, an EventEmitter3 event handler:

emitter.on('somevent', function () {
    // in this context 'this' is the same as the outer scope 'this' due to my third param to `.on()`
    // TS treats 'this' here as 'any', but I want it to be a specific type that I can specify with an annotation
    // without having to do a function bind or a self scope var.
}, this);

Hey guys. Looks like we will never figure out how statically (I mean .d.ts files) type this scope. What about to switch from config to code? Just like grunt (with tons of static configs) to gulp (with nice modular "config" in code). For example we will have _function this type callback_ witch should return type of _this_.

function getOnMethodThisType(on) {
  if (on.arguments.length === 3) {
    return on.arguments[2].type;
  }

  return TS.globalScope.type;
}

Thinking about "code instead of config", maybe we need new definition file type where instead of pseudo code config will be just js/ts code? For example

TS.class('Emitter')
  .method('on', function (on: TSMethod) {
    if (!on.callee) {
      throw new TS.Error.CalleeRequired();
    }

    switch (on.arguments.length) {
      case 2:
        return {
          "arguments": [String, Function],
          "this": TS.global.type,
          "return": on.callee.type
        };
      case 3:
        return {
          "arguments": [String, Function, Any],
          "this": on.arguments[2].type,
          "return": on.callee.type
        };
    }

    throw new TS.Error.WrongArguments();
  });

Pros: modular code, share code through npm, custom errors like "One argument is deprecated since v1.4", code is more readable than .d.ts, will solve any this problems.

:+1:

:+1: ... right now I'm using the "self workaround", and I'm not enjoying it Dx

I agree with @ivogabe at https://github.com/Microsoft/TypeScript/issues/229#issuecomment-53194534. If it is a type parameter, that means the type of 'this' can vary on a per-call basis. And if it varies, then a 'this' expression will not have a known type in the body. Better to have it be a parameter with a type annotation.

:+1:

FWIW, this is required for a lot of .d.ts files on DefinitelyTyped. Here's an incomplete list of modules that require this:

  • jquery
  • underscore
  • lodash
  • lazy.js

Also, many of the native ECMAScript APIs use bound this arguments, such as many of the Array.prototype methods and some of the String methods. JSON.{stringify,parse} call their callback with either an object or array.

// Array generics
interface Callback<T, U> {
  (this: T, value: U, index: number, array: ArrayLike<U>): any;
}

interface ArrayLike<T> {
  [key: number]: T;
  length: number;
}

interface Array<T> {
  // things...
  forEach(this: ArrayLike<T>, callback: Callback<ArrayLike<T>, T>): void;
  forEach<U>(this: ArrayLike<T>, callback: Callback<U, T>, thisArg: U): void;
  // more things...
}

// JSON
interface JSONCallback {
  (this: any[], key: number, value: any) => any;
  (this: {[key: string]: any}, key: string, value: any) => any;
}

interface JSON {
  parse(text: string, reviver?: JSONCallback): any;
  stringify(value: any, reviver?: JSONCallback | string[], space?: string | number): string;
}

// Function methods
interface Function {
  bind<T, U>(thisArg: T, ...args: any[]): (this: T, ...args: any[]) => U;
}

So, definitely :+1:

+1 Very need this feature.

This is now tracked by #3694.

+1

As an easy workaround, you can declare a variable at the top of the function, give it a type, and set it equal to this. This allows you to do type checking on it, as long as you don't mind renaming "this":

myMethod() {
    var self: MyClass = this;
    self.doStuff();
}

My work around for the last 2 years has been to just not use TypeScript. Though it's was for other issues at the time, which some of them have been fixed since v0.8.

@QueueHammer thanks man, but not terribly constructive. Did you find an alternative that does fix this problem, and how?

This is fixed by #6739 on April 8.

But isn't released yet (it is in typescript@next and will be released in 2.0).

Has this now been released as part of 2.0? Is there a set of reference examples of using this?

Yes. It is sort of covered here: https://www.typescriptlang.org/docs/handbook/functions.html

But taking an example from above:

myMethod(this: MyClass) {
    this.doStuff();
}
Was this page helpful?
0 / 5 - 0 ratings