1

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

melpomene
  • 79,257
  • 6
  • 70
  • 127
Modermo
  • 1,560
  • 1
  • 16
  • 40
  • 1
    This isn't exactly a full answer, but I wanted to point out something that many new js programmers (and myself at times) forget: functions are first class citizens meaning there is a difference between 1) `obj.printName` and 2) `obj.printName()`. 1) the value of this expression is the function itself and this is what you pass to the `addEventListener` method. 2) this expression invokes the function and therefore the value of the expression is the value that the function itself returns. in this case, that value is `undefined`. – Rico Kahler May 07 '17 at 08:14
  • Possible duplicate of [How does the "this" keyword work?](http://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work) – melpomene May 07 '17 at 08:15
  • Another thing to note is that *invoking* bind actually returns another function that you could invoke like `obj.printName.bind(obj)()`. – Rico Kahler May 07 '17 at 08:15

2 Answers2

1

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:

  1. a string of the type of event
  2. 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:

  1. boundMethod which used bind
  2. 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 thises 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

Rico Kahler
  • 12,583
  • 9
  • 41
  • 63
  • Wow, this is a really thoughtful post. I understand when to use 'this', but I still don't understand why I don't need to use ' bind' when wrapped in a function. Both are technically callbacks. – Modermo May 07 '17 at 09:54
  • @Modermo well, you don't need to use bind when wrapped in a function because the function you just wrote doesn't use `this`. it's an easy way to think about it: If your function doesn't ever use `this`, then you don't need to use bind. – Rico Kahler May 07 '17 at 10:00
  • thanks for being patient with me, I'm new to JS. The thing is, I am using 'this' in the 'printName' function. Do you mean the wrapper function doesn't use 'this'? Because the function I'm calling does use 'this' – Modermo May 07 '17 at 10:11
  • @Modermo i think i misunderstood you, I updated my answer to directly answer your question. – Rico Kahler May 07 '17 at 10:17
  • @Modermo yeah check out my updated answer. my apologizes for the many updates. wording and explaining programming needs to be just as precise as programming itself lol – Rico Kahler May 07 '17 at 10:22
  • I think I need to do some more research, because I just can't seem to figure this out right now. The way that I see it, whether wrapping in a function or immediately invoking right after the event type, the value of `this` in both is not that of the `obj` (unless of course you use `bind`). What do you mean by "method taking"? – Modermo May 07 '17 at 10:45
  • @Modermo I just mean "taking" it as in assigning the value of the function itself to a variable or passing the value of the function itself as an argument to another function. Lack of a better word, sorry – Rico Kahler May 07 '17 at 14:35
0

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.

Pankaj Shukla
  • 2,435
  • 2
  • 9
  • 17
  • No, `printButton.addEventListener('click', obj.printName() );` also passes `obj` as `this`. It just doesn't register `printName` as a click handler, it calls it immediately. – melpomene May 07 '17 at 08:24
  • @melpomene, yeah I think that is different issue, maybe a typo. He just needs to pass the function reference. – Pankaj Shukla May 07 '17 at 08:26
  • *this* is not "context". Using dot notation is just one way to set a function's *this*, there are many others (*call*, *apply*, *bind*, *new*, lexically in arrow functions, maybe others…). – RobG May 07 '17 at 08:33