Alright, I commented with some good information on this question so I might as well answer!
Functions are first class
Okay, let's starts with some fundamentals of javascript that is very dissimilar to some other programming languages: in javascript functions are first class citizens--which is just a fancy way of saying that you can save functions into variables and you can pass functions into other functions.
const myFunction = function () { return 'whoa a function'; }
array.map(function () { return x + 1; });
And because of this wonderful feature, there is a big difference between the expressions:
Expression 1
obj.printName
and
Expression 2
obj.printName();
In expression 1: the function isn't being invoked so the value of the expression is of type function
In expression 2: the function is being invoked so the value of the expression is what the function returns. In your case, that's undefined
addEventListener
The method addEventListener
takes in two arguments:
- a
string
of the type of event
- a
function
that will be run when the event fires.
Alight, so what does that mean?
When you call
// doesn't work
printButton.addEventListener('click', obj.printName() );
you're not passing a value of type function
to the addEventListener
method, you're actually passing undefined
.
// works
printButton.addEventListener('click', obj.printName.bind(obj) );
then works (for one reason) because the second argument is actually of type function
.
What does bind
do? Why does it return a function?
Now we need to discuss what bind
actually does. It related to the pointer* this
.
*by pointer, I mean a reference identifier to some object
bind
is a method that exists on every function object that simply binds the this
pointer of a desired object to the function
This is best shown by an example:
Say you have a class Fruit
that has a method printName
. Now that we know that you can save a method into a variable, let's try that. In the example below we're assigning two things:
boundMethod
which used bind
unboundMethod
that didn't use bind
class Fruit {
constructor() {
this.name = 'apple';
}
printName() {
console.log(this.name);
}
}
const myFruit = new Fruit();
// take the method `printName`
const boundMethod = myFruit.printName.bind(myFruit);
const unboundMethod = myFruit.printName;
boundMethod(); // works
unboundMethod(); // doesn't work
So what happens when you don't call bind
? Why doesn't that work?
If you don't call bind in this case, the value of the function that gets stored into the identifier unboundMethod
can be thought to be:
// doens't work
const unboundMethod = function() {
console.log(this.name);
}
where the contents of the function is the same contents of the method printName
from the Fruit
class. Do you see why this is an issue?
Because the this
pointer is still there but the object it was intended to refer to is no longer in scope. When you try to invoke the unboundMethod
, you'll get an error because it couldn't find name
in this
.
So what happens when you do use bind
?
Loosely bind
can be thought of as replacing the this
value of function with the object you're passing into bind
.
So if I assign: myFruit.printName.bind(myFruit)
to boundMethod
then you can think of the assignment like this:
// works
const boundMethod = function() {
console.log(myFruit.name);
}
where this
is replaced with myFruit
The bottom-line/TL;DR
when to use bind
in an Event Handler
You need to use Function.prototype.bind
when you want to replace the this
es inside the function with another object/pointer. If your function doesn't ever use this
, then you don't need to use bind
.
Why then don't we need to use bind in the first example?
If you don't need to "take the method" (i.e. taking the value of type of function
), then you don't need to use bind
either Another way to word that is: if you invoke the method directly from the object, you don't need bind
that same object.
In the wrapper function, you're directly invoking the method of the object (as in expression 2). Because you're invoking the method without "taking the method" (we "took" the methods into variables in the Fruit example), you don't need to use bind
.
printButton.addEventListener('click', function(){
// directly invoke the function
// no method "taking" here
obj.printName();
});
Hope this helps :D