Reputation: 1992
The following successfully prints 'foo'.
var obj = {
name: 'foo',
printName: function printName() {
console.log(this.name);
}
};
var printButton= document.getElementById('printIt');
printButton.addEventListener('click', function(){
obj.printName();
});
The following doesn't, however:
printButton.addEventListener('click', obj.printName() );
I know the solution... simply use bind
so that we're referencing the obj
object. i.e:
printButton.addEventListener('click', obj.printName.bind(obj) );
Why then don't we need to use bind
in the first example. I don't know why wrapping obj.printName()
function call in the anonymous function results in the console.log
correctly referencing and printing this
properly, but when called directly after click
, you needs to use bind
Upvotes: 1
Views: 92
Reputation: 19222
Alright, I commented with some good information on this question so I might as well answer!
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:
string
of the type of eventfunction
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
.
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
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
Upvotes: 1
Reputation: 2672
Note: You need to call printButton.addEventListener('click', obj.printName() );
without parenthesis in obj.printName()
since you want to pass the function.
The answer lies in the way this
is bound in Javascript. In JS, the way a function is called decides how this
is bound. So when you provide the callback function like below:
printButton.addEventListener('click', function(){
obj.printName();
});
Notice, printName is being called via dot
notation. This is called implicit binding rule when this
is bound to an object before dot, in this case obj
. Clearly in this case, you get the expected output.
However, when you call it like this:
printButton.addEventListener('click', obj.printName );
Notice that, all you are passing is the address
of the function that is inside obj. So in this case info about obj is lost. In other words, the code that calls back the function doesn't have the info about obj that could have been used to set this
. All it has is the address of the function to call.
Hope this helps!
EDIT:
Look at this crude
implementation I call bind2
that mimics native bind. This is just to illustrate how native bind
function returns a new function.
Function.prototype.bind2 = function (context) {
var callBackFunction = this;//Store the function to call later
return function () { //return a new function
callBackFunction.call(context);//Later when called, apply
//context, this is `obj` passed
//in bind2()
}
};
function hello() {
alert(this.name);
}
obj = {
name:'ABC'
};
var f = hello.bind2(obj);
f();
Notice: How function f() is hard bound
here. f() has hard bound this
with obj
. You cannot change this
to other than obj
now. This is another thing with bind that probably will help you knowing.
Upvotes: 0