Javascript, jQuery, Scope, and this

I want to talk a little bit about scope. Normally, scope is an easy concept, but there are a few little twists and turns in Javascript that make scope more complicated than it would seam. First, some basics.

Javascript Scope/this 101

Global Scope

//Global variable, can see by all javascript loaded.
var name = "Chris";

//A function that uses a Global variable
//This function is also Global and can be called anywhere
function sayName()
{
    alert(name);
}

//Use the Global in an alert, says "Chris"
alert(name);
//This also says "Chris"
sayName();

Anytime you declare a variable or function that is not inside of another variable or function, it’s scope is declared to be Global. This means that any code, anywhere, has access to use and modify the variable. This of course if extremely dangerous and only slightly useful. One the useful side, you could have a variable like var page_action = "refresh" that could be different depending on the page loaded and your utility functions simply use this variable. Unfortunately, it’s really easy to make a mistake, especially if the variable name is something common.

Buggy Code

//Global variable, can see by all javascript loaded.
var name = "Chris";

//A function that uses a Global varable
function sayName()
{
	name = "Kassi";
	alert(name);
}

//Use the Global in an alert, says "Chris"
alert(name);
//This now says "Kassi"
sayName();
//This also says "Kassi"
alert(name);

There are two ways to prevent this mistake. First, always use var when declaring a variable.

//Global variable, can see by all javascript loaded.
var name = "Chris";

//A function that uses a Global varable
function sayName()
{
	var name = "Kassi";
	alert(name);
}

//Use the Global in an alert, says "Chris"
alert(name);
//This now says "Kassi"
sayName();
//Now it says "Chris"
alert(name);

The other is parameters. You have to be careful, because modifying the parameter will not change the global variable should they be named the same.

//Global variable, can see by all javascript loaded.
var name = "Chris";

//A function that uses a Global varable
function sayName(name)
{
	//Parameter is used instead of the global
	alert(name);
}

//Use the Global in an alert, says "Chris"
alert(name);
//This says "Kassi"
sayName("Kassi");
//this says "Chris"
alert(name);

this

No matter where you are in this code. the this variable points to the window. Seams simple enough.

//Global variable, can see by all javascript loaded.
var name = "Chris";
var outside_this = this;

//A function that uses a Global varable
function sayName(name)
{
	var inside_this = this;

	if( outside_this === inside_this )
	{
		//Parameter is used instead of the global
		alert(name);
	}
}

//Use the Global in an alert, says "Chris"
alert(name);
//This says "Kassi"
sayName("Kassi");
//this says "Chris"
alert(name);
if( outside_this === window )
{
	alert("'this' points to the window");
}

Scope/this 102

this inside objects

Now if we create an object, this will point to the object.

//Create a global objects
var page_settings =
{
	name: "Chris",
	sayName: function()
	{
		// 'name' won't work here.
		// instead, you have to use 'this.name'
		alert(this.name);

		if( this === page_settings )
		{
			alert("'this' points to the global object 'page_settings'");
		}
	},
};

//Says "Chris"
alert( page_settings.name );
page_settings.sayName();

Here is the same thing but with a different way of creating the object.

//Create an object from a function.
function sayName(name)
{
	//Now 'this' points to this refrence of the object.
	alert(name);

	if( this === window)
	{
		alert("Won't be called because 'this' no longer points to the window");
	}
}

//Say "Chris"
var me = new sayName("Chris");
//Say "Kassi"
var gf = new sayName("Kassi");

Wait, what?

You might be wondering why this points to a window in 101, but now it points to the object. The reason is because of the new keyword. When we use it on a function, we are creating an object not just calling a function. Just like the page_settings object, this points to the instance of the object.

Scope/this 103

Events and Scope

Inside of an event function, this points the same way it did in the earlier examples.

// This function is called on a button press
// HTML is ''
function ButtonClick()
{
	// Now 'this' points to the global window object
	if(this === window)
	{
		alert("In an event, 'this' == window");
	}
}

var page_settings =
{
	// This function is called on a button press
	// HTML is ''
	sayName: function()
	{
		// 'this' points to the object as expected.
		if( this === page_settings )
		{
			alert("'this' points to the page_settings object");
		}
	},
}

jQuery events

Where things start to act a little strange is when we use jQuery to bind the events. I highly recommend using jQuery for binding events instead of onclick="myFunction();" because it’s easier to change, maintain, and jQuery takes some of the work out of it for you. But you do have to keep a few things in mind.

$(document).ready(function(){
	//Use jQuery to bind the events
	$("#fun").bind("click", ButtonClick);
	$("#objfun").bind("click", page_settings.sayName);
});

// This function is called on a button press
// HTML is ''
function ButtonClick(evt)
{
	// Now 'this' points to the HTML button
	// evt is the event object, provided by jQuery
	alert(this);
	if(this === window)
	{
		alert("this will not be called");
	}
}

var page_settings =
{
	// This function is called on a button press
	// HTML is ''
	sayName: function(evt)
	{
		// 'this' points to the HTML button NOT the page_settings object
		// evt is the event object, provided by jQuery
		alert(this);
		if( this === page_settings )
		{
			alert("this will not be called");
		}
	},
}

One of the first things you should notice is that we no longer have a reference to the page_settings object. Also note that the event parameter passed is not window.event but jQuery.Event. This is helpful because jQuery has already handled a lot of the cross browser issues for us.
Thankfully, jQuery provides an easy method for passing whatever we might need to our event handlers. You simply pass an object when you call .bind

$(document).ready(function(){
	//Use jQuery to bind the events
	//jQuery lets us pass data with the call, so we can pass the refrence to our functions/objects if we want.
	$("#fun").bind("click", {"self": ButtonClick}, ButtonClick);
	$("#objfun").bind("click", {"self": page_settings}, page_settings.sayName);
});

// This function is called on a button press
// HTML is ''
function ButtonClick(evt)
{
	// Now 'this' points to the HTML button
	// evt is the event object, provided by jQuery
	alert(this);

	//This won't be called, we didn't pass 'window', we passed 'ButtonClick'
	if(evt.data.self === window)
	{
		alert("this will not be called");
	}

	if(evt.data.self === ButtonClick)
	{
		alert("this is called, because we passed the method reference.");
	}
}

var page_settings =
{
	// This function is called on a button press
	// HTML is ''
	sayName: function(evt)
	{
		// 'this' points to the HTML button NOT the page_settings object
		// evt is the event object, provided by jQuery
		alert(this);
		if( evt.data.self === page_settings )
		{
			alert("self is now a reference to our object");
		}
	},
}

Timer Functions

So how are things effected by the two timer functions?

$(document).ready(function(){
	//Set a timer
	setTimeout( CallOne, 500);
	setTimeout( page_settings.sayName, 1000);
});

function CallOne()
{
	// 'this' equals window, just like a normal event.
	if( this === window )
	{
		alert("this is equal to the window");
	}

}

var page_settings =
{
	sayName: function()
	{
		// 'this' equals window!
		if( this === window )
		{
			alert("this is equal to the window, because timers run in a separate execution context");
		}
	},
}

When using timer events (setTimeout() or setInterval()) this always equals the window. Lucky for us, we can pass parameters to both of the methods kind’ve like we did with the jquery events.

$(document).ready(function(){
	//Set a timer
	setTimeout( CallOne, 500, CallOne);
	setTimeout( page_settings.sayName, 1000, page_settings);
});

function CallOne(self)
{
	// 'this' equals window, just like a normal event.
	if( this === window )
	{
		alert("this is equal to the window");
	}

	//but 'self' now equals a refrence to this function
	if( self === CallOne )
	{
		alert("self is CallOne()");
	}

}

var page_settings =
{
	sayName: function(self)
	{
		// 'this' equals window!
		if( this === window )
		{
			alert("this is equal to the window, because timers run in a separate execution context");
		}

		//but 'self' now equals a refrence to this object
		if( self === page_settings )
		{
			alert("self is page_settings");
		}
	},
}

That’s it

I hope you enjoyed this article. If this was helpful/useful/wrong please let me know by leaving a comment.

  • Share/Bookmark

You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

CommentLuv Enabled