2

I have this clock object:

var Clock = {
    start: function () {
      $('#btnScrollPause').show();
      $('#btnScrollResume').hide();

      advance();
      this.interval = setInterval(function () {
        advance();
      }, 5000);
    },

    pause: function () {
      $('#btnScrollPause').hide();
      $('#btnScrollResume').show();
      this.reset();
    },

    resume: function () {
      if (!this.interval) {
        this.start();
      }
    },

    reset: function () {
      clearInterval(this.interval);
      delete this.interval;
    }
  };

I have two buttons that pause and resume automatic scrolling of an element, but my confusion is on the click handlers that I've attached to them.

If I call the pause function like this:

// 1
$('#btnScrollPause').click(function () {
  Clock.pause();
});

It works correctly, however if I try to reduce the code like so:

// 2
$('#btnScrollPause').click(Clock.pause);

It no longer works and I get the error "this.reset is not defined". Also, if I make a function and use that in the click handler instead:

// 3
$('#btnScrollPause').click(scrollPause);

function scrollPause() {
  Clock.pause();
}

It works! Can someone explain why 1 and 3 work, but 2 doesn't?

Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
JB06
  • 1,881
  • 14
  • 28
  • 3
    Example-2. Inside your `pause` function `this` refers to `#btnScrollPause` and not your `Clock` object. Since `#btnScrollPause` doesn't have a function named `reset` you get an error – bassxzero Nov 21 '16 at 14:24
  • 3
    Two related (maybe dupe?) Q&As with lots of useful information on this topic are [How does the “this” keyword work?](http://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work) and [How to access the correct `this` / context inside a callback?](http://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-context-inside-a-callback) – James Thorpe Nov 21 '16 at 14:27
  • It seems so obvious now – JB06 Nov 21 '16 at 14:50

2 Answers2

4

This is related to how this keyword works in JavaScript. When you pass a method as a callback to another function the method is detached from the object and the this keyword doesn't refer to object. Additionally jQuery event methods modify the this value of callbacks to refer to the owner of the event, i.e. the DOM elements.

One option that you have is using the .bind function:

$('#btnScrollPause').click(Clock.pause.bind(Clock));

A related question: How does the “this” keyword work?

Community
  • 1
  • 1
Ram
  • 143,282
  • 16
  • 168
  • 197
2

The this object binding is volatile in JavaScript...that is, it doesn't always point to the same object and its binding can change from one line of code to the very next. How you invoke the code that contains the word this determines what object it will bind to.

Had you made an instance of Clock (i.e. var c = new Clock()) and then said:

$('#btnScrollPause').click(c.pause);

It would have worked because this would be bound to the instance of the Clock referenced by the c variable.

But as it stands with your code, this is bound to your button because that is the object that initiated the code.

Here's a checklist that you can follow to know what this will bind to...

If the code that contains this is invoked:

  1. As a method or property of an object instance (through an instance variable):

    var o = new Object(); 
    
    // "this" will be bound to the "o" object instance
    // while "someProperty" and "someMethod" code executes
    o.someProperty = someValue;
    o.someMethod();
    
  2. Via a .call(), .apply(), .bind() or Array.prototype.fn invocation:

    // "this" will be bound to the object suppled as the "thisObjectBinding"
    someFunction.call(thisObjectBinding, arg, arg);
    someFunction.apply(thisObjectBinding, [arg, arg]);
    var newFunc = someFunction.bind(thisObjectBinding, arg, arg);
    

    Additionally, several Array.prototype methods allow for a thisObject to be passed which will alter the binding for the duration of the method call:

    Array.prototype.every( callbackfn [ , thisArg ] )
    Array.prototype.some( callbackfn [ , thisArg ] )
    Array.prototype.forEach( callbackfn [ , thisArg ] )
    Array.prototype.map( callbackfn [ , thisArg ] )
    Array.prototype.filter( callbackfn [ , thisArg ] )
    
  3. If none of the other scenarios apply, Default binding occurs.

    3a. With "use strict" in effect: this is undefined

    3b. Without "use strict" in effect: this binds to the Global object

** NOTE: this binding can also be affected by using eval(), but as a general best practice, the use of eval() should be avoided.

Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • Seems I should always create a variable for any object I create that uses the `this` keyword to be safe, correct? – JB06 Nov 21 '16 at 14:53
  • @JB06 You can do that...and it does make things a bit easier at first. But the `.bind()` function is very powerful and allows you to essentially *override* `this`. That option is very helpful when you start to create more dynamic code. This is also true with the `Array.prototype` methods I've mentioned above. I think getting used to (and embracing) a non-static `this` binding is the key. By the way `.call()` and `.apply()` are no longer recommended as `.bind()` effectively replaces them. – Scott Marcus Nov 21 '16 at 14:55