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.
- Do you know why this happens?
- 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);
}
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)));
};
};
}
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.
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() })
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