1

What does 'this' keyword refer to when used in global 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("SomeGlobalObject.show()", 1000);

everything works ok.

But if we do something like:

AppendEvent(window, 'load', SomeGlobalObject.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?

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);
  }
Marco Demaio
  • 33,578
  • 33
  • 128
  • 159

4 Answers4

5

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 () { SomeGlobalObject.show() } );

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

AppendEvent(window, 'load', SomeGlobalObject.show.bind(SomeGlobalObject, 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))); 
    }; 
  };
}
Marco Demaio
  • 33,578
  • 33
  • 128
  • 159
Andy E
  • 338,112
  • 86
  • 474
  • 445
  • 2
    ...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 Mar 30 '10 at 13:50
  • @bobince: thanks, I've been looking for that :-) I really need to switch my default search provider back to Google. – Andy E Mar 30 '10 at 13:52
1

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
  • 10,137
  • 2
  • 31
  • 53
0

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
  • 34,724
  • 14
  • 83
  • 123
0

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
  • 2,304
  • 18
  • 19