Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JQuery deferred changes "this"

I'm attempting to have a javascript object run a deferred method and when it's .done() call a function in that same object. I'm having issues because "this" becomes the deferred object instead of the object that called it.

PageObject.prototype.successFunction = function() {
  console.log(arguments);
  return console.log(this.name + " Success function called");
};

PageObject.prototype.loadPage = function(url) {
  return $.when($.mobile.loadPage("pages/" + url))
    .done(this.successFunction);
};

var pg = new PageObject();
pg.loadPage("test.html");

How do I send "this" into the successFunction? This PageObject is going to be extended by others as well, so knowing "this" when running successFunction will be very handy.

It seems simple, and probably has a simple answer. I was looking into .apply() but I'm not sure it helped. This post on stack overflow was helpful a little bit, but it broke the minute I put it into the .done() function.

Functions as parameters (with parameters) -- JavaScript

like image 638
Chris Avatar asked Sep 06 '12 01:09

Chris


2 Answers

jQuery's proxy will return a function bound to a specific context:

PageObject.prototype.loadPage = function(url) {
  return $.when($.mobile.loadPage("pages/" + url))
    .done($.proxy(this.successFunction, this));
};

There is also ES5's bind which operates similarly but needs to be shimmed in older browsers

PageObject.prototype.loadPage = function(url) {
  return $.when($.mobile.loadPage("pages/" + url))
    .done(this.successFunction.bind(this));
};

apply and call can execute a function immediately with the specified context, but proxy and bind return a function that can be used later or passed to other functions.

like image 188
Dennis Avatar answered Oct 22 '22 06:10

Dennis


The this "variable" in JavaScript is called the "calling object" and is determined when its enclosing function is called (not when it's defined). It can be set a few different ways:

  1. You can set it by using the . operator (e.g. myObject.myFunction())
  2. You can set it by using a function's call() or apply() methods
  3. You can (under ES5) bind it permanently by using the function's bind() method
  4. It will default to the global object if none of the above methods set it to something else

The important thing to understand is that (aside from method 3 above), it's all about how the function is called when it's called, not how it's referenced, not how it's stored, not where it's created.

In your case, you're passing this.successFunction as a function reference to be called by jQuery, but it doesn't call it that way (because it just got a reference to the function, not any information on how that function should be called).

There are a few fancy tricks you can use with jQuery's $.proxy() or ES5's .bind() methods, but when it comes down to it, the most straightforward way to handle it is to simply retain this through a closure-scoped variable and use a function wrapper:

PageObject.prototype.loadPage = function(url) {
    var self = this;
    return $.when($.mobile.loadPage("pages/" + url))
            .done(function () { self.successFunction(); });
};

Note that we're using method 1 to explicitly bind successFunction's calling object to be the same as loadPage's calling object. It's short, simple, clear, works fine under ES3, and doesn't depend on jQuery.

like image 34
Pete Avatar answered Oct 22 '22 05:10

Pete