John Oxley
John Oxley

Reputation: 14990

Typescript: How do I get the real "this"

I'm going through the learn.knockoutjs.com tutorial and I'm at step 4 of the custom bindings tutorial. There is the following JavaScript:

update: function(element, valueAccessor) {
    // Give the first x stars the "chosen" class, where x <= rating
    var observable = valueAccessor();
    $("span", element).each(function(index) {
        $(this).toggleClass("chosen", index < observable());
    });
}

I have translated it to the following TypeScript:

update: (element, valueAccessor) => {
    var observable = valueAccessor();
    $("span", element).each(index => {
        $(this).toggleClass('chosen', index < observable())
    });
}

This creates the following, where a _this variable is created to preserve the scope of the "update" function, rather than the internal "span" function.

update: function (element, valueAccessor) {
    var _this = this;
    var observable = valueAccessor();
    $("span", element).each(function (index) {
        $(_this).toggleClass('chosen', index < observable());
    });
}

The problem is with $(_this). How do I get TypeScript to give me the real $(this).

Upvotes: 1

Views: 928

Answers (3)

Esailija
Esailija

Reputation: 140230

Well the typescript doing _this is the more intuitive one for many, in fact code like this causes multiple duplicates per day on Stackoverflow:

  update: function() {
     $(elem).click(function(){
          this.save();
     });
  }

this is not the same this because in js this works differently from other languages - it has a separate binding for every function call.

Typescript would make the above work as expected by many newcomers to js:

  update: function() {
     //this is the object with update, save methods etc
     $(elem).click( () => {
          this.save();  //this is still the object with update, save methods etc
     });
  }

There is usually a way to refer to what you want even when using fat arrow:

update: function() {
    $(elem).click( (event) => {
        $(event.currentTarget).hide() //same as $(this).hide() with normal function
        this.save(); //Stays intuitive to non-javascripters
    });
}

Or with your example:

update: function() {
    $("span", element).each( (index, elem) => {
        $(elem).toggleClass('chosen', index < observable())
        this.save(); //stays intuitive to non-javascripters
    });
}

Of course for experienced Javascripters that are used to the JS-behavior of this, it might not be so intuitive.

Btw I am not 100% on the typescript syntax...

Upvotes: 3

Michael Geary
Michael Geary

Reputation: 28850

Esailija has the right idea here: don't use $(this) in the callback at all. Your code will be much cleaner with a named parameter.

As another example of how that helps, consider this simple jQuery plugin:

// Set a random opacity on each of the elements in a jQuery object
jQuery.fn.randomOpacity = function() {
    return this.each( function() {
        $(this).css({ opacity: Math.random() });
    });
};

Ouch. Can a piece of code possibly get any more confusing than that? this appears on two lines in a row, and it means completely different things.

Instead, use the named parameter:

// Set a random opacity on each of the elements in a jQuery object
jQuery.fn.randomOpacity = function() {
    return this.each( function( i, element ) {
        $(element).css({ opacity: Math.random() });
    });
};

That is much more clear.

Upvotes: 1

John Oxley
John Oxley

Reputation: 14990

The answer is very simple. Do not use a lambda.

update: (element, valueAccessor) => {
    var observable = valueAccessor();
    $("span", element).each(function (index) {
        $(this).toggleClass('chosen', index < observable())
    });
}

Note that the difference is that this code uses ... element).each(function (index)... instead of the lambda

I do not understand why so if someone could post why, that'd be great.

Upvotes: 1

Related Questions