views:

120

answers:

4

What does 'this' keyword refer to when used in gloabl object?

Let's say for instance we have:

  var SomeGlobalObject =
  {
     rendered: true,
     show: function()
     {
        /*
        I should use 'SomeGlobalObject.rendered' below, otherwise it 
        won't work when called from event scope.
        But it works when called from timer scope!!
        How can this be?
        */
        if(this.rendered)
           alert("hello");
     }
  }

Now if we call in an inline script in the HTML page:

SomeGlobalObject.show();
window.setTimeout("Msg.show()", 1000);

everything work ok.

But if we do something like

AppendEvent(window, 'load', Msg.show);

we get an error because this.rendered is undefined when called from the event scope.

  1. Do you know why this happens?
  2. Could you explain then if there is another smarter way to do this without having to rewrite every time "SomeGlobalObject.someProperty" into the the SomeGlobalObject code?

Thanks!

AppendEvent is just a simple cross-browser function to append an event, code below, but it does not matter in order to answer the above questions.

  function AppendEvent(html_element, event_name, event_function)
  {
        if(html_element.attachEvent) //IE
           return html_element.attachEvent("on" + event_name, event_function);
        else
           if(html_element.addEventListener) //FF
              html_element.addEventListener(event_name, event_function, false);
  }
+1  A: 

Do this

var SomeGlobalObject =
  {
     ...
  }

AppendEvent(window, 'load', function(){
    SomeGlobalObject.show();
});

Javascript supports dynamic scoping. So SomeGlobalObject will be available to the function declared inline always.

Zoidberg
+4  A: 

When you reference a function that is a method of an object, you're detaching it from that object and this will no longer be a reference to the object.

The easiest solution is to wrap it in an anonymous function:

AppendEvent(window, 'load', function () { Msg.show() } );

There's also the bind method available to functions in ECMAScript 5th Edition implementations, which allow you to do this:

AppendEvent(window, 'load', Msg.show.bind(Msg, arg1, arg2));

The JS framework, Prototype, provides this method to current JS implementations too. The code (thanks @bobince):

// From Prototype.js
if (!Function.prototype.bind) { // check if native implementation available
  Function.prototype.bind = function(){ 
    var fn = this, args = Array.prototype.slice.call(arguments),
        object = args.shift(); 
    return function(){ 
      return fn.apply(object, 
        args.concat(Array.prototype.slice.call(arguments))); 
    }; 
  };
}
Andy E
...or add it yourself if it's missing. http://stackoverflow.com/questions/2025789/preserving-a-reference-to-this-in-javascript-prototype-functions/2025839#2025839
bobince
@bobince: thanks, I've been looking for that :-) I really need to switch my default search provider back to Google.
Andy E
A: 

The this keyword always refers to the calling object. In the first example, SomeGlobalObject is the caller.

I believe you would need to do something like AppendEvent(window, 'load', function() { SomeGlobalObject.show() })

richardtallent
A: 

A simple way to describe what happened:

'this' always refers to the invoker of the function.

So take the offending case:

AppendEvent(window, 'load', Msg.show);

Event handling is invoked by the window. So 'this' becomes window and

window.rendered

is undefined.

bind() will quickly become your best friend :-)

As an aside

window.setTimeout("Msg.show()", 1000);

will run a little quicker if you supply the function object directly

window.setTimeout(Msg.show, 1000);

This is because the first syntax requires eval() of the string - basically compiling - before it can be invoked

plodder