-2

I'm trying to create a custom module. I'm using prototype to add additional methods. The module will have an event, in which I will have to access a method from the module with 'this'. I will do that using bind.

I ran into a slight problem. I created a method, then using prototype, I assigned the method to a variable with the bind method. The problem is, 'this' isn't the one I passed through bind. Here's the sample code:

JSFiddle

function MyPlugin() {
  this.hello = 'hello'
  document.addEventListener('click', this.clickedBinded);
  this.clickedBinded('e');
}

MyPlugin.prototype.clickedBinded = clicked.bind(this);

function clicked(e) {
  console.log(this.hello, e);
}

var plugin1 = new MyPlugin();

How can I get clickedBinded have this as this? Meaning, I don't want this to be Window, I want it to be MyPlugin.

Update

The reason why I did: MyPlugin.prototype.clickedBinded = clicked.bind(this) and not: document.addEventListener('click', this.clicked.bind(this));, is because I'm going to have to remove the event at some point. And as this answer sais, if you're using bind in an event handler, and you want to remove the event, you have to assign the method with bind to a variable.

Community
  • 1
  • 1
Jessica
  • 7,791
  • 10
  • 44
  • 89
  • 3
    actaully, `this` is exactly what you passed through to `bind` ... look at the code, what do you think `this` is when the that line of code is executed? it will be `window` or `null`, but never an object that hasn't even been instantiated yet – Jaromanda X Jul 15 '16 at 02:50
  • Your code behaves as expected. It seems you are completely misunderstanding `this`. Probably, you want just `MyPlugin.prototype.clickedBinded = clicked`. – Oriol Jul 15 '16 at 02:52
  • @Oriol That's what I thought, but it doesn't work for calling `addEventListener`. – Barmar Jul 15 '16 at 02:56
  • @Barmar OK, I only saw the `clickedBinded` call, but not the event listener. Then I would bind the listener, as jfriend says. – Oriol Jul 15 '16 at 02:59
  • @JaromandaX Thanks! That explains why it was `window`. Can't believe I didn't catch that! (Just learning about `this`, and `bind`.) I updated the question. – Jessica Jul 15 '16 at 13:49
  • @Oriol Updated the question, and explained why I did what I did. – Jessica Jul 15 '16 at 13:50
  • @Barmar Updated the question. – Jessica Jul 15 '16 at 13:50
  • @Jessica Just store the bound listener in your instance – Oriol Jul 15 '16 at 13:56
  • @Oriol Can you please show me an example, I don't quite understand what to do. – Jessica Jul 15 '16 at 13:59

2 Answers2

2

.bind() has to be called AFTER the object you want it to be bound to has already been created and when you're in the right context to have a reference to the desired object. You are calling .bind() when your code is first loaded. That will not have a meaningful value for this. Instead, change your code to this:

function MyPlugin() {
  this.hello = 'hello';
  // save bound function so you can use it to remove the listener later
  this.fn = this.clicked.bind(this);
  document.addEventListener('click', this.fn);
  this.clicked('e');
}

MyPlugin.prototype.clicked = function(e) {
  console.log(this.hello, e);
}

// method that will remove the event listener
MyPlugin.prototype.stopClick = function() {
    document.removeEventListener('click', this.fn);
}

var plugin1 = new MyPlugin();

Notice in this fixed version that .bind(this) is called ONLY when we already have this that corresponds to our current object.


In your version, you were calling clicked.bind(this) when your code was first loaded and this would have a global value which in strict mode would be undefined and in non-strict mode in the browser would be window. The object that you will create with new MyPlugin() doesn't even exist yet when you were calling clicked.bind(this) so there's obviously no way that this could contain the appropriate value.


If you want to be able to remove the event listener later, then just store the bound listener so you can use it later.

jfriend00
  • 580,699
  • 78
  • 809
  • 825
  • Can you turn your code into a stack snippet so we can run it? – Barmar Jul 15 '16 at 02:57
  • @Jessica - No problem. You just save the bound reference so you can use it later to remove the event handler. See the updated code in my question. Next time, describe the actual problem when you post your first question. – jfriend00 Jul 15 '16 at 14:32
  • Thanks! Is there a way of doing `this.fn = this.clicked.bind(this);` using `prototype`? – Jessica Jul 17 '16 at 15:21
  • @Jessica - I don't know what you mean by "using prototype". – jfriend00 Jul 17 '16 at 16:21
  • Instead of `this.` is there a way of doing it with `MyPlugin.prototype.` – Jessica Jul 17 '16 at 21:41
  • @Jessica - No, there is not. When you pass `this.clicked` as a callback, the value of `this` is NOT passed with it. Only a reference to the method itself is passed. Thus, when the callback is later called, it will not be called in the right way to set the proper value of `this`. So, you have to do something to `bind` the right value of `this` for your callback. There are several different techniques for doing that, but none use only the prototype. See the [6 ways that the value of `this` is set in this answer](http://stackoverflow.com/a/28016676/816620). – jfriend00 Jul 17 '16 at 21:53
0

As jfriend00 explained, you should bind the event listener inside the constructor. Each instance will have a different listener.

If you want to be able to remove the listener, store it in the instance:

var click = document.getElementById('click'),
    remove = document.getElementById('remove');
function MyPlugin() {
  this.hello = 'hello';
  var listener = clicked.bind(this);
  click.addEventListener('click', listener);
  remove.addEventListener('click', function self() {
    click.removeEventListener('click', listener);
    remove.removeEventListener('click', self);
  });
}
function clicked(event) {
  console.log(this.hello);
}
var plugin1 = new MyPlugin();
<input type="button" id="click" value="Click me" />
<input type="button" id="remove" value="Remove listener" />
Oriol
  • 225,583
  • 46
  • 371
  • 457