There are many churlish titles that I could use for this post, but for now I'll moderate my juvenile sense of humor. Today I'm just going to show a simple JavaScript function that pokes around a 'this' to see what it smells like. First off I'm not really planning to go to the level of the ECMA Script specs, nor really am I planning a thorough explanation of the object-like structures of JavaScript functions. I'm going to cover the basics of understanding context, and how to use it, and how it can cause headaches at some point. For a more detailed explanation, check out K Scott Allen's blog he's got some really good posts on JavaScript scoping.
What I'm interested is in talking briefly about why in yesterday's post I wrote the following:
extender = Sys.Application.findComponent(extenderID);
extender._lyt = extender._layout;
extender._layout = function layoutExtension(){
alert('a');
if (_doLayout && this._lyt)
{
alert('g');
_doLayout = false;
this._lyt();
}
};
The red section is what I'm focusing on, why and what does the "this" mean here, and why don't I just write something like:
function doLayout(){
alert('a');
if (_doLayout && this._lyt)
{
alert('g');
_doLayout = false;
_lyt();
}
};
var _lyt = null;
function() ….{
extender = Sys.Application.findComponent(extenderID);
lyt = extender._layout;
extender._layout = doLayout;
The real answer lies in the extenders' function _layout():
_layout : function() {
/// <summary>
/// Position the modal dialog
/// </summary>
…
var xCoord = 0;
var yCoord = 0;
if(this._xCoordinate < 0) {
var foregroundelementwidth = this._foregroundElement.offsetWidth? this._foregroundElement.offsetWidth: this._foregroundElement.scrollWidth;
Notice the reference to the this object. If I were to set extender._layout equal to a function defined in the scope of the page, when I delegate down to the _layout function, the this object wouldn't have a foregroundElement to interrogate. However when I essentially extend the original like this, extender._lyt = extender._layout the this object refers to the extender itself. Unlike the friendly world of C# where this always refers the object that contains the actual code, this in this case is much more akin to something generated using the Reflection.Emit or code generation functionality. It's all about the context of execution. Because JavaScript is such a fuzzy language, it's easy to get tripped up on where variables and functions are scoped. I've tripped myself up more than once by using the same var name in both a global and local context, or worse still by re-declaring a function or variable. Unfortunately I don't have any great tips for avoiding pitfalls like that, other than when you put a page together from multiple controls that each might have their own JavaScript, take the time to open up Fiddler or some other JS Debugger and look at what's on the page.
Being that I'm somewhat of a trial-and-error based web developer, when I was tinkering around with the code above, actual did forget to take execution context into account. Basically I use the rule that a this in the scope of a function, always refers to the containing object, if it's the page, the containing object is the document, if it's a property or prototype on function, its containing object is its parent function. This translates something like this; if you see some code x.foobar = this.myvar; this refers not to x but to x 's containing object; whereas x.foofoo = function(){ alert(this.toString());} this refers to x. This is because the this is being referenced inside (in the scope or execution context of) something that is defined as a method/property/extension/prototype of x.
Okay so the last thing I want to toss in is a simple function I use sometimes to figure out what this I'm really working with. In reality any JS object can be tossed at it, and it will pour out its contents. Although this function dumps its output to a <div/> it's pretty easy to extend it to dump its output to an Xml string, and then in turn push that into a grid, with links that allow drilldowns, and pretty soon you have a very simple JS Watch window, but I'm the type who likes quick and dirty, so here it is. Basically it just pokes an object to see what's been hung on it, and dumps the output to a div. It's somewhat tweaked to poke AjaxToolkit objects whit the extra get_ checks, but that can be pulled or other customizations like checking specific HTML Properties/Attributes could be added, hope this is useful to someone.
USAGE
… code …
checkthis(this);
… more code …
function checkthis(obj, useAlertBit){
if(!obj){
throw 'Cannot evalutate null';
}
var crlf = (useAlertBit) ? '\r\n' : '<br />';
var error = '';
var output = 'obj - Properties' + crlf
+ getVal(obj, 'id') + crlf
+ getVal(obj, '_id') + crlf
+ 'Type: ' + typeof(obj) + crlf+ crlf;
for(var i in obj){
try {
output += i + ': ' + obj[i] + crlf;
if (i.indexOf('get_') != -1 && obj[i]){
output += i +'-Value: ' + obj[i]() + crlf + crlf;
}
else{
output += crlf;
}
} catch (e) {
//alert(e.message);
error += 'Error: ' + i + ' - ' + e.message + '\r\n';
}
}
if (error != ''){
alert(error);
}
if (useAlertBit){
alert(output);
}
else {
var dv = document.createElement('div');
document.documentElement.appendChild(dv);
dv.style.width = '1024px';
dv.style.height = '800px';
dv.style.overflow = 'auto';
dv.style.position = 'absolute';
dv.style.visibility = 'visible';
dv.style.display = '';
dv.style.backgroundColor = '#ffffcc;';
dv.style.border = 'solid 1px #0000cc';
dv.style.top = '50px';
dv.style.left = '5px';
dv.style.zIndex = '100';
dv.title = 'Double Click to close';
dv.ondblclick = new Function( "document.documentElement.removeChild(this);disposeObj(this);" );
dv.innerHTML = '<pre>' + output + '</pre>';
}
}
function getVal(obj, key){
if (obj[key] != 'undefined'){
return key +': ' + obj[key];
}
return key +': N/A';
};
function disposeObj(obj){
obj = null;
}
Print | posted on Wednesday, October 31, 2007 10:45 PM