Chords
Chords

Reputation: 6860

Accessing variable in callback function... what?

I've been through a ton of posts and I finally got what I needed thanks to this:

$("a.foo").click(function(){
    var that = this;
    jPrompt("Type something:","","", function(r) {
        $(that).text(r);
    }
}

From the following: Accessing $(this) within a callback function

I was wondering if someone could expand on what exactly is happening here (why is this not available without re-assigning?) and what core information I should read up on? From what I gather this might have something to do with closures... that's most of what I bumped into while searching around. Is that accurate?

In my case, I was looking to execute some code, then redirect once an ajax request completed. In the callback function I was running $(this).attr("href") which was returning undefined.

Upvotes: 1

Views: 543

Answers (4)

Nathan Wall
Nathan Wall

Reputation: 10614

A Short Overview of this

this in JavaScript is dynamically scoped. Its behavior differs from all other variables which are lexically scoped. Other variables don't have a different binding depending on how the function is called; their scope comes from where they appear in the script. this however behaves differently, and can have a different binding depending not on where it appears in the script but on how it's called. Consequently, it can be a source of confusion for people learning the language, but mastering it is necessary in order to become a proficient JavaScript developer.

Since this is dynamically bound there are several ways to change its values based on how you call the function.


Examples

When you execute a function in JavaScript, the default this is window.

function foo() {
    console.log(this);
}

foo(); // => window

The this value can be changed in a number of ways. One way is to call the function as a method of an object:

var x = {
    foo: function() {
        console.log(this);
    }
};
x.foo(); // => This time it's the x object.

Another way is to use call or apply to tell the function to execute in the context of a certain object.

function foo() {
    console.log(this);
}
foo.call(x); // => x object again
foo.apply(x); // => x object as well

If you call or apply on null or undefined, the default behavior will occur again: the function will be executed in the context of window:

function foo() {
    console.log(this);
}
foo.call(null); // => window
foo.apply(undefined); // => window

However, note that in ECMAScript 5 strict mode, this does not default to window:

(function() {

    'use strict';

    function foo() {
        console.log(this);
    }

    foo(); // => undefined
    foo.call(null); // => null
    foo.apply(undefined); // => undefined

})();

You can also set the this by using bind to bind the function to an object before it is called:

function foo() {
    console.log(this);
}

var bar = {
    baz: 'some property'
};

var foobar = foo.bind(bar);

foobar(); // => calls foo with bar as this

Going Father: Lazy Bind / Uncurrying this

Going further, you may sometimes want to take functions which act on a this and allow the this value to be passed in as the first argument to the function. This can be really helpful for Array methods, such as forEach. For instance, let's say you are dealing with an object which is array-like but not actually an array.

var arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    'length': 3
};

If you want to iterate over this object with forEach, you could use call:

Array.prototype.forEach.call(arrayLike, function(item) {
    console.log(item);
});
// Logs: a, b, c

However, another option is to create a forEach function which can be called directly on your object:

var forEach = Function.prototype.call.bind(Array.prototype.forEach);

Now you can use this function anytime you want to iterate over an array-like object:

forEach(arrayLike, function(item) {
    console.log(item);
});
// Logs: a, b, c

Sometimes this method is referred to as "uncurrying this". However, I prefer to create a function which can generate these "uncurried" functions and call it "lazy binding".

var lazyBind = Function.prototype.bind.bind(Function.prototype.call);

var forEach = lazyBind(Array.prototype.forEach);
var slice = lazyBind(Array.prototype.slice);
var map = lazyBind(Array.prototype.map);

forEach(arrayLike, function(u) {
    console.log(u);
});
// Logs: a, b, c

var realArray = slice(arrayLike);
// Converts arrayLike into a real array

forEach(
    map(arrayLike, function(u) {
        return u + 'Q';
    }),
    function(u) {
        console.log(u);
    }
);
// Logs: aQ, bQ, cQ

One really awesome thing about this technique is it can be useful for creating securable JavaScript, which can be helpful if you don't want other scripts on the page snooping around your internal variables. This is a pretty advanced meta-programming technique, though, and you don't see it in day-to-day JavaScript.

Upvotes: 1

Miltos Kokkonidis
Miltos Kokkonidis

Reputation: 3996

The code in the question with some added comments:

$("a.foo").click(function(){
    var that = this; //`this` holds the a object clicked.  now so does `that`! 
    jPrompt("Type something:","","", function(r) { 
        //even if `this` has a different value here, `that` still holds the a object clicked
        $(that).text(r);
    }
}

This is something you will often find yourself doing in similar situations. this is context-dependent and you often need to keep the value this had in one context and use it in another.

A quote from the ECMAScript specification:

10.1.7 This

There is a this value associated with every active execution context. The this value depends on the caller and the type of code being executed and is determined when control enters the execution context.

Hope that answers your question. You also asked for a resource for further reading. Please visit:

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/this

These guys provide excellent documentation both detailed and typically quite accurate (unlike other popular sources of reference that often comes first in Google searches -- w3cshools.com I am thinking of you!).

Upvotes: 1

Sampson
Sampson

Reputation: 268344

The meaning of this changes depending on where you're at. The this within a handler for your click event means something other than the this within the callback passed to your jPrompt function.

For what it's worth, you don't need to re-assign this, since the event object passed into your handler will have a reference to the currentTarget:

$("a.foo").on("click", function (event) {
    // 'this' here refers to the anchor we clicked
    jPrompt("Type something:", "", "", function (r) {
        // 'this' here refers to whatever jPrompt instructs
        $(event.currentTarget).text(r);
    }
}

Upvotes: 1

jfriend00
jfriend00

Reputation: 707308

this is assigned by javascript according to how a function is called. So, it is the jPrompt() function that determines what value this will have in your callback when jPrompt() calls the callback.

So, unless jPrompt goes out of its way to keep the same value for this via some argument you passed in, it will likely have a different value. As such, you can save it away for access within the callback as you've done. This is a very common design pattern in javacscript callbacks.

FYI, some of the ways that this is assigned:

  • obj.method() - this in method() will be set to obj
  • func.call(obj) - this in func() will be set to obj
  • func() - this will be set to window in func() or undefined in strict mode

Upvotes: 3

Related Questions