Quantcast
Channel: HTML5
Viewing all articles
Browse latest Browse all 663

Javascript: This, That And The Other Thing

$
0
0

I talked before about the special this variable in Javascript. And I intimated that it's more complicated than it seems.

Now, this seems like a straightforward concept. It refers to the object that "owns" this function. But figuring out that "ownership" can be a little tricky.

As we saw before, if that function is part of an object, then the object owns it. Otherwise, as a sort of a fallback, the global object (window in browsers) owns it. For example,

> function f() {return this}
> f() == window
true
> o = {f: function f() {return this} }
> o.f() == window
false
> o.f() == o
true

What if we don't actually write the function body in the object, like this?:

> o = {f:f}
> o.f() == window
false
> o.f() == o
true

That works as hoped. Javascript essentially copies the function into the object, or at the call site considers whether it's being called from an object or not. Now here's where it gets confusing. Suppose inside f() we have a nested function g() that returns this. At first glance one might expect that it would inherit this from it's parent function, unless it were over-ridden by being in a local object:

> function f() {
...   function g(){
...       return this;
...   }
...   return g()
...}
> o = {f:f}
> o.f() == window
true
> o.f() == o
false

Unfortunately it's not what you might've expected. In fact it defaults to the global object, even if it's not a globally available function. Why is this? Well, it actually makes sense when you step back a bit. I think the confusion comes from thinking of the function f() as a class, while thinking of g() as a member, but remember, this isn't C++. In fact, both f() and g() are functions, and every function has a this (and arguments, but I'll get to that later). There's nothing special about a nested function, it still needs a this variable, based on how it's called, and like any other function, if it's not called from an object, it defaults to the global object, window. When you think about it that way, it wouldn't make any sense at all for the inner function to inherit the this pointer any more than it would to inherit its argument list. The inner function's this shares the same name and takes priority, so there's no way to access the enclosing function's this. And yet it would be handy to have access to it in many circumstances.

Well, I'm not the only person to have wanted something like that, in fact there is a standard pattern to deal with this situation. While this is not inherited in the scope of the nested function, local variables are. All you need to do is assign this to a local variable before calling the nested functions that need access to this. By convention, this variable is often called that:

> function f() {
...   var that = this;
...   function g(){
...       return that;
...   }
...   return g();
...}
> o = {f:f}
> o.f() == window
false
> o.f() == o
true

But of course the story is even more complicated when you dig down deep enough. A while back I mentioned that I liked reading the C code produced by the early C++ compiler. When I looked at the C code for a member function, what I found was a plain old fashioned C function with a mangled name, but instead of the expected parameters passed in, there was an extra one at the beginning, corresponding to the the this pointer. It's the same in Javascript. There's no magic, this is passed in as the first parameter to every function, it's just normally normally determined by the runtime system, and it typically defaults to the global object if there is no other owner. However, in Javascript it turns out that, if you're really desperate, there are ways to control the value of this at the call site, rather than relying on the runtime system to do it for you.

Like everything else in Javascript, functions are objects, and they have properties. Like arrays, which have a special length property, Function objects have some special built-in properties. In particular, they have a call property, which can be used to make this hidden first parameter explicit. When you invoke a function's call property, it's just like calling the function, but you provide an extra first parameter which the function takes as it's this variable. There are a few caveats, one being that the runtime really wants an object there, so as in other contexts it will create one if you don't give it one explicitly. For example if you pass in the number 3 as your first parameter, it will actually put a Number object there, but I would like to think that you know better than to do that anyway. (And if you're really excited about it, you could look up the apply and bind properties, but I'm not going to talk about them right now.)

If you refrain from passing anything to call() (or if you passs undefined), the runtime will still insert a default value, the global object (window in browsers), otherwise whatever argument you put first in line will be the this value:

> function f() {return this;}
> f()
true
> f.call() == window
true
> f.call("hello") // actually creates a string object
[object String]
> o = f.call({a:1})
> o.a
1

If you're still reading by this point, you're probably considering that, while fascinating, this all seems a bit esoteric and confusing. But if you're writing HTML5 apps there is at least one very common case in which you might encounter this confusion on a regular basis.

When I first started playing with simple interaction on HTML apps and web pages, I couldn't quite get the hang of when I could use this reliably and when I couldn't. I'd do something like this:

function turnRed() {
    this.style.color = "red";
}
b = document.getElementById('clickme');
b.onclick = turnRed;

. . .            // later, in HTML:
<div id="clickme">Click Me!</div>

And I'd click on it and the text would turn red and all was right with the world. Other times, I'd do this:

<div onclick="turnRed()">Click Me!</div>

And I'd click on it and nothing would happen. Sometimes I'd change it to 'onclick="turnRed(this)"' and then I'd have to change the turnRed function to take an argument instead of using this, and it would work again but I'd be fuddled. I assume by now it's obvious what happened. When b.onclick is assigned the turnRed function, it's owned by b, which is the div element of interest, so this.style actually means somethnig. When it's called in the HTML onclick attribute, it's like calling it without any owning object, so it defaults to the global object, window, which has no style attribute. Putting it all together, if I really want to use the HTML onclick attribute without messing up my function, I could just do this:

<div onclick="turnRed.call(this)">Click Me!</div>

Finally the other thing. It turns out that there is one other special variable that every function has and, like this, is not inherited by an inner nested function: the arguments variable. For each function call, in addition to the this variable, which describes the owner of the function, the arguments variable describes the arguments that were passed to the function, regardless of the formal parameters. This can be used when you want a variable number of arguments in a function, you can use this object to access all of the actual arguments passed at the call site, whether more or fewer than the formal list in the function. If you want to access these from a nested function, you'll need to copy it to a local variable, as was done with 'that = this'. You can call it whatever you want (within reason), but args would probably be a good suggestion.

  • html5
  • javascript
  • Javascript This variable
  • Javascript Function Call
  • 图标图像: 


    Viewing all articles
    Browse latest Browse all 663


    <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>