Let's first look at why we need this. In this example, one "method" is trying to call into another:
function A() {
// A's constructor
}
A.prototype.bar = function() {...}
A.prototype.foo = function() {
// bar() -- calling bar() directly won't work.
this.bar() // this works.
}
Here, for foo() to call into bar(), it needs to use the this pointer. Why? It has to do with scopes and variable visibility. It's worth noting before we proceed that a function declaration does nothing more than create a variable whose name is the function's name and whose value is a Function object. In fact,foo(arg) {
// function body
}
is just syntactic sugar forvar foo = function(arg) {
// function body
}
So when we talk about function name visibility we're really talking about variable name visibility, and the same rules apply. A variable is visible if it is defined in the current function, any containing functions, or the global context.var a=trueThe way this works under the covers is that each scope, that is to say, each function, has an invisible object associated with it called the activation object. It is used as a placeholder for declared variables. It is nothing more than an regular old object with a property for each variable you declare. The activiation object is invisible because you can't reference it directly. So when you say var foo=true, you're actually setting the foo property on the activation object to true. When you start nesting functions, you create a "chain" of activation objects. A variable name is visible if any of the objects in this chain contain a property matching the variable name you're trying to resolve. The ECMAScript specification calls this chain the "scope chain". As you might expect, there is an activation object for the global scope as well, and it is always the last item in the scope chain.
// I can see a but not b or c
function foo(){
var b=true
// I can see a and b, but not c
function bar(){
var c=true
// I can see a,b,c
}
}
Identical visibility rules apply for resolving function names. Let's go back to the first example where foo was trying to call bar:
function A() {
// A's constructor
}
A.prototype.bar = function() {...}
A.prototype.foo = function() {
// bar() -- calling bar() directly won't work.
this.bar() // this works.
}
There are two objects in foo's scope chain. The first is the foo function's own activation object, and the second is the global activation object. Notice that the bar function was not actually attached to the global activation object. It was attached to A.prototype. Since bar is nowhere to be found in foo's scope chain, it won't resolve by itself. You would instead call this.bar(). Technically from foo() we could have called into bar like this: A.prototype.bar(), but that would hand bar a different this pointer.Which brings us to the slippery this pointer. Can you spot the problem with the following code?
function Widget()Notice the onSuccess function. It is attempting to call Widget's handleResponse function, but will fail since at the time the function is invoked, 'this' actually does not refer to our Widget instance. Here's the gotcha, straight from the ECMA spec:
{
// Widget object constructor
}
Widget.prototype.doRequest = function()
{
Ajax.Request('/someurl', {
onSuccess: function(response) {
this.handleResponse(response)
}
}
}
Widget.prototype.handleResponse = function(response)
{
// handle response
}
The caller provides the this value. If the this value provided by the caller is not an object (including the case where it is null), then the this value is the global object.The caller provides the this value. It does this with the standard dot notation, as in foo.bar(). The thing in front of the dot becomes this inside the function. What that means is that a function has to know how it will be called in order to make use of the this pointer. So really when you call a function, you're passing one more argument than you think. The thing in front of the dot is in a very literal sense passed to the function just like the arguments between the parentheses. It is an extra argument that happens to be referenced by a variable called this.
If you call a bare function name without using a dot, so that the function name is unqualified, then the this pointer that gets passed becomes the global object, which in the case of web pages is the window object.
So that's it. Just remember to think of this as a literal argument you pass into the function that happens to come before the dot instead of between the parentheses. See if you can predict what will happen in the following code snippets:
function A(){}
A.prototype.foo = function()
{
// who's this?
}
var a = new A();
var b = a.foo
a.foo()
b()
On the first invocation of foo, this will refer to a, since a was before the dot. On the second invocation, it will refer to the window object, since it was invoked without a dot.Let's go back to the Ajax call we used in the earlier example. Let's assume we're using a 3rd party Ajax library like the one prototype provides, and we call into their code, passing a callback function.
Ajax.Request('/someurl', {
onSuccess: function(response) {
// what's this now?
this.handleResponse(response)
})
In this case, we don't actually know what the this pointer is going to be. The only way to know for sure is to look at the code that invokes the callback function and see what's in front of the dot, and that's internal to the 3rd party library. In any event, it's not going to point to anything we care about. So how can we fix the Widget example so it will work? By saving the this pointer we care about in another variable, like this:Widget.prototype.doRequest = function()By copying this into another variable when it has the value we want, we can safely access the copy later and be sure nothing has changed.
{
var that=this
Ajax.Request('/someurl', {
onSuccess: function(response) {
that.handleResponse(response)
}
}
}
I hope this has been helpful.
//rb

No comments:
Post a Comment