Kanpeki
Kanpeki

Reputation: 63

Saving "this" when binding a function from a class

I have a simple class in JavaScript:

function MyCounter() {
  this.counter = 0;
  $('#button').click(this.run);
}

MyCounter.prototype.run = function() {
  this.counter += 1;
  return console.log(this.counter);
};

This class is invoked like that:

var myCounter = new MyCounter();

HTML includes a single clickable button with ID="button". Clicking this button is supposed to increment an internal variable inside a myCounter instance. Obviously, it fails because this.counter does not exist, because at the time on execution of bound handler this equals to event, not myCounter instance.

A crude hack to overcome this is to save "this" to some other variable and wrap calling actual handler into a anonymous function:

function MyCounter() {
  this.counter = 0;
  var this2 = this;
  $('#button').click(function() {
    this2.run();
  });
}

Is there a better, cleaner way? Or at least, may be there's an universal agreement / style guide on how to name such "temporary this" variables?

Upvotes: 3

Views: 533

Answers (3)

demux
demux

Reputation: 4654

This isn't really considered crude in javascript. One common way of doing something like this would be:

function MyCounter() {
  this.counter = 0;

  var self = this;
  this.run = function() {
    self.counter += 1;
    console.log(self.counter);
  };

  $('#button').click(this.run);
}

Using the es6 syntax a common pattern would be to bind the function in the constructor:

class MyCounter {
  constructor() {
    this.counter = 0;
    this.run = this.run.bind(this);

    $('#button').on('click', this.run);
  }

  run() {
    this.counter += 1;
    console.log(this.counter);
  }

  // And now you can also remove the event listener:
  destroy() {
    $('#button').off('click', this.run);
  }
}

But this pattern could also be applied to the old way of creating js classes:

function MyCounter() {
  this.counter = 0;
  this.run = this.run.bind(this);

  $('#button').on('click', this.run);
}

MyCounter.prototype.run = function() {
  this.counter += 1;
  console.log(this.counter);
}

// And again you can also remove the event listener:
MyCounter.prototype.destroy = function() {
  $('#button').off('click', this.run);
}

This is nice because you can now access the run method from the prototype before initiating the class.

But I think the bottom line here is that you can't bind something to the class instance before it has been created.

Side note: In all of my examples, the run method is bound to the class instance when it's initiated. This means that the run method does not need to be bound again i.e. $('#button').click(this.run.bind(this));.

Yet another pattern (a singleton, with private variables):

var myCounter = (function() {
  var counter = 0

  function run() {
    counter += 1
    console.log(counter)
  }

  $('#button').on('click', run)

  return {
    run: run,
    destroy: function() {
      $('#button').off('click', run)
    }
  }
})()

Since there is only one instance of #button, there is no reason to have the option of multiple class instances.

Upvotes: 1

Jefftopia
Jefftopia

Reputation: 2155

I'm not sure what it is, but I'm just not fond of the bind function. As an alternative, you can scope this to the object instance by wrapping it in an anonymous function, then use HTML 5's onclick property to bind the function to the template.

plunkr demo

var MyCounter = (function () {
    function MyCounter(counter) {
        this.counter = counter || 0;
    }
    MyCounter.prototype.run = function () {
        return alert(this.counter += 1);
    };
    return MyCounter;
}());


var counterInstance = new MyCounter(10);
window.counterInstance = counterInstance;

...

<button onclick="counterInstance.run()">Test</button>

Another strategy - a more OO approach - would be to pass the element reference to the class itself and assign the method in the class constructor. That would look something like:

class Counter {

    constructor(elementRef, counter = 0) {
        this.element = elementRef;
        this.counter = counter;

        //native HTML type
        this.element.onclick = () => this.run();

       //alternatively
       this.element.addEventListener('click', this.run);
    }

    function run() => { return this.counter++; }

}

Upvotes: -1

jfriend00
jfriend00

Reputation: 707456

You can just use .bind():

$('#button').click(this.run.bind(this));

.bind() creates a mini stub function that essentially does this for you:

var self = this;
$('#button').click(function() {
    return self.run();
});

Upvotes: 2

Related Questions