How should the 'this' keyword be implemented to prevent confusion

Topics: Language Specification
Aug 21, 2013 at 7:46 AM
I would like to add some more fuel to the 'this' discussion. Let's look at the example below
    /** Bind the change event of the dataSource to the reselect routine */
    bindToDataSource(): void
    {
        // The => causes the 'this' keyword to point to the instance of the class.
        this.dataSource.bind('change', () => 
        {
            this.reselectSelected();
        });
    }

    /** Reselect all remembered rows */
    reselectSelected() : void
    {
        var toSelect = [];
        var rows = this.getAllRows();
        rows.each((e) =>
        {
            var dataItem = this.dataItem(this);
            if (dataItem != undefined)
            {
                var pos = $.inArray(dataItem.get('Id'), this.selected);
                if (pos !== -1)
                {
                    toSelect.push(this);
                }
            }
        });

        this.local++;
        try
        {
            this.select(toSelect);
        }
        finally
        {
            this.local--;
        }
    }
Web essentials is pretty misleading here. For each 'this' in the 'reselectSelected' when I hover over with the mouse it tells me it is the CttGrid object but in fact it's not. The sentence 'var dataItem = this.dataItem(this);' already show my discrepancy in thinking because the first 'this' should be the class instance and the second 'this' the object from the 'each' function. It is translated to this
    CttGrid.prototype.bindToDataSource = function () {
        var _this = this;
        this.dataSource.bind('change', function () {
            _this.reselectSelected();
        });
    };

    CttGrid.prototype.reselectSelected = function () {
        var _this = this;
        var toSelect = [];
        var rows = this.getAllRows();
        rows.each(function (e) {
            var dataItem = _this.dataItem(_this);
            if (dataItem != undefined) {
                var pos = $.inArray(dataItem.get('Id'), _this.selected);
                if (pos !== -1) {
                    toSelect.push(_this);
                }
            }
        });

        this.local++;
        try  {
            this.select(toSelect);
        } finally {
            this.local--;
        }
    };
Although it generated the '_this = this' sentence as a means to get the class object this is not the case. The 'this' in the 'reselectSelected' routine is of type 'dataSource' when the routine is entered. I had to refactor the routine to the version below.
    /** Bind the change event of the dataSource to the reselect routine */
    bindToDataSource(): void
    {
        // The => causes the 'this' keyword to point to the instance of the class.
        this.dataSource.bind('change', () => 
        {
            this.reselectSelected(this);
        });
    }

    /** Reselect all remembered rows */
    reselectSelected(me: CttGrid) : void
    {
        var toSelect = [];
        var rows = me.getAllRows();
        rows.each((e) =>
        {
            var dataItem = me.dataItem(this);
            if (dataItem != undefined)
            {
                var pos = $.inArray(dataItem.get('Id'), me.selected);
                if (pos !== -1)
                {
                    toSelect.push(this);
                }
            }
        });

        me.local++;
        try
        {
            me.select(toSelect);
        }
        finally
        {
            me.local--;
        }
    }
The code is translated to a version supporting the CttGrid via the parameter 'me'.
    CttGrid.prototype.bindToDataSource = function () {
        var _this = this;
        this.dataSource.bind('change', function () {
            _this.reselectSelected(_this);
        });
    };

    CttGrid.prototype.reselectSelected = function (me) {
        var _this = this;
        var toSelect = [];
        var rows = me.getAllRows();
        rows.each(function (e) {
            var dataItem = me.dataItem(_this);
            if (dataItem != undefined) {
                var pos = $.inArray(dataItem.get('Id'), me.selected);
                if (pos !== -1) {
                    toSelect.push(_this);
                }
            }
        });

        me.local++;
        try  {
            me.select(toSelect);
        } finally {
            me.local--;
        }
    };
Understandably the first example will cause confusion (as it did with me). Especially the fact that 'Web Essentials' tells me the wrong thing.
Aug 26, 2013 at 2:17 PM
Who shares my opinion?
Developer
Aug 26, 2013 at 6:38 PM
Web Essentials is a separate plugin not maintained by the Typescript team. The Visual Studio language service provided by the Typescript plugin shows me all the 'this' uses in your first example to be the class instance, which is correct based on how arrow functions work. The compiler can't do any flow control analysis based on bind and apply calls. I'm not sure what you mean by this:

"The sentence 'var dataItem = this.dataItem(this);' already show my discrepancy in thinking because the first 'this' should be the class instance and the second 'this' the object from the 'each' function."

They are the same 'this' value.
Aug 29, 2013 at 11:07 AM
Edited Aug 29, 2013 at 11:09 AM
You hit the nail on the head. That is exactly my point. To be able to call the method the first 'this' has to be an instance of the CttGrid class while you would expect the second 'this' to contain the item from the 'each()' function. They can not be different at the same time and thus my discrepancy.
Developer
Aug 30, 2013 at 1:11 AM
The semantics of 'this' here are just the same as JavaScript. TypeScript cannot alter that. Arrow functions provide some syntactic sugar for preserving the value of 'this' from an outer scope (rather than having to manually do an assignment like 'var that = this' and then capture 'that' in a closure).

I still don't understand why you think the second 'this' should be the value from the 'each' iteration. The item for the 'each' function is already explicitly named in your code, it's 'e', it would never be 'this'.
Aug 30, 2013 at 6:41 AM
The => should in all cases return the instance of the class. This can be accomplished by storing the 'this' in the '_this' as is done for functions by the TypeScript compiler but this does not work for callback scenarios. I would like to see a construction that causes the 'this' in a => construction to always be the instance of the class.
Aug 30, 2013 at 1:52 PM
I didn't understand also what you are saying. In your code, "this" means the same thing in both places. Perhaps your example is wrong and does not show the issue?

Also, storing this in _this won't do what you think. Consider:
public class Foo {
    _this = this;
} 
Will be compiled to:
    this._this = this;
When your function runs in another context, "this" will be another object, and this._this will not exist, so your method won't work at all even if you did what you think it should happen.

The way it works today, setting this to the context of the parent, is the best you can do with javascript.
Sep 3, 2013 at 6:41 AM
Edited Sep 3, 2013 at 6:46 AM
Ok, the confusion amongst you is complete as it was with me. The example is exactly what you say it is: it is wrong. The 'this' can never have 2 different meanings in the same place, I am aware of that. That is not the point of the discussion. Forget the example for a moment. Here is what I am suggesting.

I would like it, if technically feasible, to see that the => contruction causes the 'this' to always be the instance of the class no matter where you put it.

The way it is now is that it may be the class instance but it can also be something else. When you use it directly insinde a method of a class it is the instance of the class but when you use it in f.i. a callback scenario it most probably is not.
Sep 3, 2013 at 10:16 AM
Edited Sep 3, 2013 at 10:18 AM
It is not technically feasible. The context depends on the caller, not on the function being called. For this reason, there would be no place to store the "this" the way you imagine.

On the other hand, you could just "bind" all methods of the class to "this" in the constructor as a workaround. But then, arrow functions wouldn't be needed as "this" would always be the same.

Also, I don't believe this would be a good idea as a general case, because this would create a lot of overhead unnecessarily. It would have a proxy function of every function in each instance, and a lot of the times you wouldn't need it.

This is not a big issue though. your problem is actually related to how you handle the situation. If you need to setup a callback or return a function, just use an arrow function and it will be bound.

If you have no control of how the function is called (for example knockout events, where you just name the function that will be called), just call "bind" in the constructor.
Sep 3, 2013 at 11:37 AM
Edited Sep 3, 2013 at 11:37 AM
Well if it is not technically feasible then this discussion ends here. It would however make TypeScript a lot easier to understand. I do agree to the need to understand how JavaScript works before you can program with TypeScript but it would solve the confusion you get using the 'this' and expect it to be the class instance.
Coordinator
Sep 4, 2013 at 10:14 PM
You can use a field initializer with the fat arrow lambda syntax if you have a class member that you know is going to be used in a callback position:
class MyClass {
    x = 5;
    
    public normalFunction() {
        // Might have correct 'this', might not, depending on how it's called
    }
    
    public boundFunction = () => {
        console.log(this.x); // Guaranteed to be correct 'this'
    }
}
Trade-offs: There is non-trivial overhead with this (1 extra closure per method per class instance) so you probably only want to use it where it's actually needed. It is also impossible to call boundFunction through a super call like you could with normalFunction.