Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Fixing "this" with bind()

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

So how can we fix this? If we're going to be fancy and use objects in JavaScript, I don't want to have to worry about whether or not this is actually this in each function! That's no way to live! Nope, I want to know confidently that inside of my whatIsThis function, this is my RepLogApp object... not a random array of pets and their noises.

More importantly, I want that same guarantee down in each callback function: I want to be absolutely sure that this is this object, exactly how we'd expect our methods to work.

And yes! This is possible: we can take back control! Create a new variable: var boundWhatIsThis = this.whatIsThis.bind(this):

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
initialize: function($wrapper) {
... lines 71 - 81
var newThis = {cat: 'meow', dog: 'woof'};
var boundWhatIsThis = this.whatIsThis.bind(this);
... line 84
},
... lines 86 - 125
};
... lines 127 - 131
</script>
{% endblock %}

Just like call(), bind() is a method you can call on functions. You pass it what you want this to be - in this case our RepLogApp object - and it returns a new function that, when called, will always have this set to whatever you passed to bind(). Now, when we say boundWhatIsThis.call() and try to pass it an alternative this object, that will be ignored:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
initialize: function($wrapper) {
... lines 71 - 81
var newThis = {cat: 'meow', dog: 'woof'};
var boundWhatIsThis = this.whatIsThis.bind(this);
boundWhatIsThis.call(newThis, 'hello');
},
... lines 86 - 125
};
... lines 127 - 131
</script>
{% endblock %}

Try it out: refresh! Yes! Now this is this again!

Binding all of our Listener Functions

Delete that debug code. Now that we have a way to guarantee the value of this, all we need to do is repeat the trick on any listener functions. In practice, that means that whenever you register an event handling function, you should call .bind(this). Add it to both event listeners:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
initialize: function($wrapper) {
... lines 71 - 72
this.$wrapper.find('.js-delete-rep-log').on(
... line 74
this.handleRepLogDelete.bind(this)
);
this.$wrapper.find('tbody tr').on(
... line 78
this.handleRowClick.bind(this)
);
},
... lines 82 - 118
};
... lines 120 - 124
</script>
{% endblock %}

Replacing this in Event Listeners

But wait! That's going to totally mess up our function: we're relying on this: expecting it to be the DOM Element object that was clicked! Dang! But no problem, because we already learned that this is equal to e.currentTarget. Fix the problem by adding var $link = $(e.currentTarget):

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
... lines 70 - 90
handleRepLogDelete: function(e) {
e.preventDefault();
var $link = $(e.currentTarget);
... lines 95 - 113
},
... lines 115 - 118
};
... lines 120 - 124
</script>
{% endblock %}

Now just change the $(this) to $link:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
... lines 70 - 90
handleRepLogDelete: function(e) {
... lines 92 - 93
var $link = $(e.currentTarget);
$link.addClass('text-danger');
$link.find('.fa')
... lines 98 - 101
var deleteUrl = $link.data('url');
var $row = $link.closest('tr');
... lines 104 - 113
},
... lines 115 - 118
};
... lines 120 - 124
</script>
{% endblock %}

And life is good!

Try it out! Refresh, click, and winning!

Finally, we can fix something that's been bothering me. Instead of saying RepLogApp, I want to use this. We talked earlier about how RepLogApp is kind of like a static object, and just like in PHP, when something is static, you can reference it by its object name, or really, class name in PHP.

Always Referencing this, instead of RepLogApp

But that's not going to be true forever: in a few minutes, we're going to learn how to design objects that you can instantiate, meaning we could have many RepLogApp objects. For example, we could have five tables on our page and instantiate five separate RepLogApp objects, one for each table. Once we do that, we won't be able to simply reference our object with RepLogApp anymore, because we might have five of them. But if we always reference our object internally with this, it'll be future proof: working now, and also after we make things fancier.

Of course, the problem is that inside of the callback, this won't be our RepLogApp object anymore. How could we fix this? There are two options. First, we could bind our success function to this. Then, now that this is our RepLogApp object inside of success, we could also bind our fadeOut callback to this. Finally, that would let us call this.updateTotalWeightLifted().

But wow, that's a lot of work, and it'll be a bit ugly! Instead, there's a simpler way. First, realize that whenever you have an anonymous function, you could refactor it into an individual method on your object. If we did that, then I would recommend binding that function so that this is the RepLogApp object inside.

But if that feels like overkill and you want to keep using anonymous functions, then simply go above the callback and add var self = this:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
... lines 70 - 90
handleRepLogDelete: function(e) {
... lines 92 - 103
var self = this;
$.ajax({
... lines 106 - 113
});
},
... lines 116 - 119
};
... lines 121 - 125
</script>
{% endblock %}

The variable self is not important in any way - I just made that up. So, it doesn't change inside of callback functions, which means we can say self.updateTotalWeightLifted():

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
... lines 70 - 90
handleRepLogDelete: function(e) {
... lines 92 - 103
var self = this;
$.ajax({
... lines 106 - 107
success: function() {
$row.fadeOut('normal', function() {
... line 110
self.updateTotalWeightLifted();
});
}
});
},
... lines 116 - 119
};
... lines 121 - 125
</script>
{% endblock %}

Try that! Ah, it works great.

So there are two important takeaways:

  1. Use bind() to make sure that this is always this inside any methods in your object.
  2. Make sure to reference your object with this, instead of your object's name. This isn't an absolute rule, but unless you know what you're doing, this will give you more flexibility in the long-run.

Leave a comment!

25
Login or Register to join the conversation
Peter Avatar
Peter Avatar Peter | posted 4 years ago | edited

Wow, how weird is JS, anyway, as an excirsise I thought I would do the more involved version of the anonymous function to onRowDelete, I finally got it to work and I can see why you chose the other solution.

I ended up having to do the self=this trick to make another anonymous function work correct, it would seem even if you use bind to make JS behave, you still end up having to keep your eye out for anonymous functions and be very aware of the this object and what it's pointing too.

This was my result, did I miss something?


handleRepLogDelete: function (e) {
      e.preventDefault();
      let $link = $(e.currentTarget);


      $link.addClass('text-danger');
      $link.find('.fa')
            .removeClass('fa-trash')
            .addClass('fa-spinner')
            .addClass('fa-spin');
  let deleteUrl = $link.data('url');
  let $row = $link.closest('tr');
  $.ajax({
     url: deleteUrl,
     method: 'DELETE',
            success: this.onRowDelete.bind(this, $row)
      });
},
onRowDelete: function($row){
       let self = this;
       $row.fadeOut(function () {
              $row.remove();
              self.updateTotalWeightLifted();
      });
},
Reply

Hey Peter!

Ha! Yea, weird stuff, right! Fortunately, if you keep going to the next tutorial about ES6 and the arrow function, we'll be able to work around all of this in a cleaner way (but because you got through this stuff, you'll really understand how this "this" business works).

So let's check out your solution. I love the this.onRowDelete.bind(this, $row) - that IS the correct way to do this by using bind. You also could have done something crazy like this:


success: function() {
    this.onRowDelete($row);
}.bind(this)

Bananas, right!? You're creating an anonymous function, and so "this" should not be "this" inside of it. But, we bind that anonymous function to this... which allows us to use "this" inside :p.

Anyways, about the let self = this part in onRowDelete(), if you wanted to avoid that, you would do a similar trick:


$row.fadeOut(function () {
    $row.remove();
    this.updateTotalWeightLifted();
}.bind(this));

I hope that makes sense (and that I didn't screw anything up 😉)

Cheers!

Reply
Peter Avatar

Thanks Ryan
That really helps me understand.. mind you I just finished the ES6 course and had to have a moment with ES6 in a dark room for some alone time and some klennex..

I love the other two solutions too, its all about binding it to the function to make sure this is availble as what its been set to before the anonymous function! Thank you for teaching me all that.

Once I finished that it made everything simpler to implement, but knowing how this works is super very important in my opinion.

Reply
Graymath technology Avatar
Graymath technology Avatar Graymath technology | posted 4 years ago

Hi Ryan
Earlier on you mentioned that we are not adding the () to the handlers within in initialize function. Now we are calling the bind function so was "this" the reason earlier on of not calling it directly?

Reply

Hey Graymath technology!

Not quite - but I think this is a good question :). Forget about the "bind" thing for a moment. So, we have some code like this:


this.$wrapper.find('.js-delete-rep-log').on(
    'click',
    this.handleRepLogDelete
);

You need to think of this.handleRepLogDelete as a variable that points to a function. We're basically saying:

Hey jQuery! When this element is clicked, here is the function that you should call.

We don't do this in PHP often, but it's possible. It would be like this:


// I'm just a variable that points to a function
$myFunction = function($str) {
    echo $str;
}

// I'm calling that function!
$myFunction('Call that function!');

So, regardless of the "bind" stuff, what we want to do in JavaScript is pass a reference of a function to jQuery - not actually call that function when we are telling jQuery that we want to listen to some on "click" event. When we add bind later, it's really an unrelated change: we still want to pass a reference to a function, not call that function. When you write this.handleRepLogDelete.bind(this), the bind() function creates a new function and returns a reference to that function. So, we're still passing a reference to a function to jQuery, just a slightly different function that guarantees that "this" will be what we expect :).

I hope that helps! I have problems with this sometimes because PhpStorm likes to auto-complete the ()

Cheers!

Reply

Hey Ryan,
I realize that arrow function didn't exist when this video came out, but seeing all those grossed out people in the comments makes me feel like an appendix to it could be appropriate.
Thanks!

Reply

Hey there!

It wasn't that the arrow function didn't exist... but rather that we chose to show that in the *next* tutorial - all about the ES6 features: https://knpuniversity.com/s.... Of course, these days, the arrow function is becoming so universal, it should indeed be shown earlier and earlier :). Oh, but bind() does still have a place - even in our new React tutorial, we often use bind to guarantee that callback methods will *always* be bound to this, regardless of how their called. But, the arrow function definitely removes a lot of ugliness.

Cheers!

Reply

Hey Ryan,
thanks for your reply!
I understand that you wanted to keep ES6 things separate. On the other hand, when beginning to learn JavaScript, it must feel like an insane language due to this "this" thing (among other weird things). I hope that doesn't put off beginners from exploring more! :)

Reply

Hey @asd

Indeed JS is a weird programming language, but after you unveil its mysteries, it's actually fun to work with and especially if you are working with ReactJS

Cheers!

Reply
Thao L. Avatar
Thao L. Avatar Thao L. | posted 5 years ago

This just shows me how bad a language javascript is.

Reply

Hey Thao L.
I would preffer to say how *weird* javascript is, but, after you know how it works internally you end-up loving it ;)

Cheers!

Reply
Default user avatar
Default user avatar jian su | posted 5 years ago | edited

Hi Guys:

Can we just have`
var self = this`
as RepLogApp variable. so that you don't have to bind each internal function. so you don't need to have this link`
var $link = $(e.currentTarget);`
.
when you call an RepLogApp function, you just need to do the following`
self.handleRepLogDelete`
. Shouldn't this would be easier?`
var newThis = {cat: 'meow', dog: 'woof'}; var boundWhatIsThis = this.whatIsThis.bind(this);`
from the code above, it confused me because when I explicitly call() in JavaScript, I would expect this would be 'newThis'. Call() in JavaScript has a meaning of changing context to 'this'

Reply

Hey Jian,

Do whatever you like most. I think it's easier for you because you just get used to it :) But bind() feature allow you to get rid of "var self = this", and actually, the function behave itself as PHP functions where $this refers to the current object, not to something that calls this function.

Cheers!

Reply

Hey there would you please share your PHPStorm color schema with us? and maybe some other options too

Reply

Hey Mesut,

We use Darcula PhpStorm theme in our recent screencast, including this one. Check this free tutorial about PhpStorm to know more interesting features in PhpStorm we love and use from screencast to screencast:
https://knpuniversity.com/s...

Cheers!

Reply
Max S. Avatar

Hey Ryan!
Are your suggestions concerning *this* actually js best-practices? Or would you rather see them as 'a great way to deal with js for php geeks'?

Reply

Hi Max!

If you are referring to use .bind() to guarantee that "this" is this, then absolutely. I was just digging around in the core of a really complex JS library this morning (webpack) and you could see it being used there :).

If you're referring to using var self = this; - this is not (anymore) considered a great practice... but you need to wait until the next tutorial about ES6 to have a better solution. With the tools we have up to now, it's the only way to solve it!

And if you're referring to something entirely... let me know ;).

Cheers!

1 Reply
Max S. Avatar

Hey Ryan! Cool! I was simply wondering as you are trying to refer to PHP al the time (which is great!)...

Best

Max

Reply

Using .bind() just made JS even more weird for me, tbh

Reply

boykodev

I got you, but that's a detail about callbacks, sometimes you need to control the reference to "this", or you would end up with an unwanted behaviour. In short, JS is WEIRD in general

Have a nice day

1 Reply

Yeah, that's what I've figured. You too!

1 Reply
Avraham M. Avatar
Avraham M. Avatar Avraham M. | posted 3 years ago

Hello!

var self = this;
didn't work, getting error:

TypeError: self.updateTotalWeightLifted is not a function

Debugging shows "this" is a clicked delete link, but not the desired RepLogApp object.
I work in phpstorm, set to ES6 and then to ES5 yet same result.
What I miss?

Thanks
==========
I found, forgot to bind(this)
this.handleRepLogDelete.bind(this)
in initialize function.
Thanks

-1 Reply

Haha, nice, you figured out what was the problem. Javascript is kind of a weirdo :p

Reply
Maik T. Avatar
Maik T. Avatar Maik T. | posted 4 years ago | edited

what about using arrow functions to not loose focus of this? look at the following example


    // this => RepLogApp
    $.ajax({
        uri: deleteUrl,
        method: 'DELETE'
        succses:  () => { 
            $row.fadeOut('normal', () => {
                $row.remove();
                // this allready RepLogApp
                this.updateTotalWeightLifted();
            });
        }
    });
-1 Reply

Hey Maik,

Yeah, good point! We love arrow functions, really - they're our favorite! And you can write them fast - fewer code to type :p But we just do not explain them yet in this course if I'm correct, so we just didn't want to complicate things here. But yeah, if you know the difference and see you can use the arrow function instead - use it! Well, sometimes regular functions might have some valid use cases when better use *them*, but probably most of the time you can use arrow functions instead.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony... but since it's a JavaScript tutorial, the concepts are still ? valid!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.1.*", // v3.1.10
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.1
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.4.0
        "symfony/monolog-bundle": "^2.8", // 2.12.0
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.1
        "symfony/phpunit-bridge": "^3.0" // v3.1.6
    }
}
userVoice