Reputation: 6860
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
Reputation: 10614
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.
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
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
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
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
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 modeUpvotes: 3