JavaScript Scope Adjustment
There are often times in JavaScript where you need scope adjustment, but you don’t really know it. In this entry I’m going to show you just how to do it, and I’ll be using the oldest trick in the book to get it done (which by the way isn’t really a trick, but at least there’s a cool function involved), and in the end, I’ll give an example of the right way to get it done.
A typical code scenario
As developers and afficionados of abstraction, we like the this keyword because it gives us the ability to transform at any given time. The fact of the matter is, this could be anything at anytime depending on varying situations. Let’s take this generic function for instance:
Sample usage of ‘this’ abstraction
function makeBlack() {
this.style.backgroundColor = 'black';
}
Now not to confuse the purpose of this function as its goal is not to make you like Barry White (which is also confusing); But rather to make whatever this is (hopefully a valid DOM reference {HTMLElement}), and then set its background color to black. Make sense so far?
Lets make this work in action with a valid set up where that when a user clicks on a box, we’ll run the makeBlack function on it. And just for kicks we’ll use my favorite event utility to attach the listener methods. See the example:
event listener to run makeBlack
<head>
. . .
function assignListeners() {
YAHOO.util.Event.on('myElement','click',makeBlack);
}
function makeBlack() {
this.style.backgroundColor = 'black';
}
YAHOO.util.Event.on(window,'load',assignListeners);
. . .
</head>
Wonderful. That was easy. Hey, if we wanted we could go nuts with the Event utility and have makeBlack get assigned to all the colors of the rainbow. It would the anti-careBears delight:
assigning makeBlack on the rainbow
<head>
. . .
function assignListeners() {
YAHOO.util.Event.on(['red','blue','purple','orange','green','yellow'],'click',makeBlack);
}
function makeBlack() {
this.style.backgroundColor = 'black';
}
YAHOO.util.Event.on(window,'load',assignListeners);
. . .
</head>
Now anytime we click on any of our boxes, they instantly become black. But we’ve already had enough on the Event utility, and besides, you can learn all about its uses on why this thing kicks mucho bottom, the point of this article is to learn scope adjustment.
And so the problem arises
We don’t want this to get run based upon some event getting fired. No no no. We want the freedom to run this makeBlack function whenever we darn please. The problem is, however, how do we tell it what this is? Afterall, it was easy with the Event utility. In those cases, this was whatever triggered the event. For example if you clicked on the purple box, then this was purple. And if blue, then it was blue.
A possible solution (not really)
One proposal to fixing this problem is to simply rewrite our makeBlack function to accept an {HTMLElement} DOM reference as an argument, and then just run the same procedures on that instead of this. Thus our revised makeBlack function would look like this.
revised makeBlack function
function makeBlack(oElement) {
oElement.style.backgroundColor = 'black';
}
The problem with this however is that now it can’t be used as a listener like this:
revised makeBlack function
function assignListeners() {
// this will cause the function to fire right away and fail to assign the listener
YAHOO.util.Event.on('myElement','click',makeBlack(someOtherElement));
}
function makeBlack(oElement) {
oElement.style.backgroundColor = 'black';
}
So much for that…
A Solution that works
Lets go back to square one where our code looked pretty. However this time we’re going to add an intermediary function which will intercept our listener event, and then adjust our scope along the way to run the makeBlack function with a different reference of this.
For the sake of having a scenario, we’ll assign the intermediary function to fire when we ‘click’ on the ‘red’ box; which will in turn run the makeBlack function with an adjusted scope of this to be ‘blue’ (not red).
Our Base Logic
<head>
. . .
function assignListeners() {
YAHOO.util.Event.on('red','click',intermediary);
}
function intermediary() {
// run makeBlack with 'this' being 'blue'...
// Um... how do we do that?
}
function makeBlack() {
this.style.backgroundColor = 'black';
}
YAHOO.util.Event.on(window,'load',assignListeners);
. . .
</head>
That old trick thingy that’s not really a trick
Right, so I guess I’ve dragged this out long enough. A simple way to fire with scope
can be achieved like so:
fire with scope
var myElement = document.getElementById('blue');
myElement['foo'] = myFunction;
myElement['foo']();
It’s as easy as that. And if you’re going to be doing it often, it’s best just to turn it into a function. So for the lack of a better name, we can call this fire_this_function_with_this_scope (ok, that’s just too damn long). How ’bout fireWithScope. We can then use this in our final product:
Final Logic with fireWithScope included
function tester() {
YAHOO.util.Event.on('red','click',intermediate);
}
function intermediate() {
fireWithScope(document.getElementById('blue'), makeBlack);
}
function fireWithScope(el, fn) {
el['e'+fn] = fn;
el['e'+fn]();
}
function makeBlack() {
this.style.backgroundColor = 'black';
}
YAHOO.util.Event.on(window,'load',tester);
Awesome. Ain’t life grand now that we’ve learned how to do that? Oh right, you want to know what happened. Basically in our new fireWithScope function we’re assigning the actual function we want to fire to our element with an extended property of ‘e’ plus the function name (just in case you’re wondering - yes - that can be overridden, but chances are, it won’t), then immediately afterward, we fire that method straight off our element reference (with the scope of the element (which ultimately becomes ‘this’ in the function)).
The Real Solution
(Updated) With the above examples looking fine and dandy and almost downright clever, there are two methods that are built-in right into the language (thanks Simon). These methods are call and apply. This was indeed an oversight the first run through, but it indeed has become our real solution
. For the purposes of this demo, we’ll go ahead and use apply even though both will work in this example.
So first things first, let’s rewrite our group of functions with apply in action and send our fireWithScope function back to the submarines (it was never that cool of a function name anyway). Take a look at our final piece of code:
apply method adjusting scope
function tester() {
YAHOO.util.Event.on('red','click',intermediate);
}
function intermediate() {
makeBlack.apply(document.getElementById('blue'));
}
function makeBlack() {
this.style.backgroundColor = 'black';
}
YAHOO.util.Event.on(window,'load',tester);
By now we should be thoroughly happy with the end result. And as always, I’ve put together a purdy demo of Sending & Adjusting Scope in JavaScript. Have a beautiful and enlightening day.




May 3rd, 2006 at 12:21 am
You can also do this using the call and apply methods built in to the language:
Cheers,
Simon
May 3rd, 2006 at 6:50 am
I’ll echo what Simon said. Also, in Prototype, there is the bind and bindAsEventListener methods.
May 3rd, 2006 at 8:01 am
Another nice way to solve that particular type of problem is to use partial application in which you write a function that returns a function for you which prefills the argument using a closure…still with me? :)
function assignListeners() {
// this will cause the function to fire right away and fail to assign the listener
YAHOO.util.Event.on(’myElement’,’click’,makeBlackener(otherElement));
}
function makeBlackener(element) {
return function() {
makeBlack(element);
}
}
function makeBlack(oElement) {
oElement.style.backgroundColor = ‘black’;
}
That has the advantage of this still pointing to the originating element which in many cases is very useful.
As it is it will leak some memory in IE if the event hanlders aren’t cleaned up but that’s fixable if you care about that kind of thing. I use partial application quite heavily for this kind of work though.
May 3rd, 2006 at 8:09 am
Jonathan, the first thing that came to mind was the Prototype binders, I remember Ryan Campbell pointing them out as a way to adjust the scope for the Observers, but definitely as Simon pointed out, it’s probably just best to use the built in methods JavaScript provides.
In all the cases, they all work which is nice.
May 3rd, 2006 at 8:33 am
‘Scuze me while I post one more comment. Simon, these are the kind of days where you smack yourself in the head and have to wonder what you (as in me) were thinking. Today was too long of a day at work.
Otherwise, thanks for pointing the call and apply methods out. I really should write an update article now. If at the least, it shows you how scope adjustment works when assigning functions to DOM references :)
May 3rd, 2006 at 8:46 am
fireWithScope, I like the name. Sounds like launching a torpedo from a submarine. “Man your battle stations, periscope up. Ready, fireWithScope!” Anyway, nice writeup. Thanks for keeping it simple for people like me.
May 3rd, 2006 at 10:44 am
What about just setting a reference to “this” and putting a pointer function into the addEvent call itself?
I posted recently about (passing parameters into an addEvent call, and would love to know if I have gone about this the wrong way.
May 3rd, 2006 at 11:46 am
Eric, there’s nothing wrong with your approach; As a matter of fact, that’s the same approach that Palmer has taken with the Prototype library. Snook mentions it in the second comment.
May 3rd, 2006 at 12:06 pm
What are the best IE5.01 implementations of call and apply?
May 3rd, 2006 at 3:08 pm
Why doesn’t
function makeBlack(oElement) {
oElement.style.backgroundColor = ‘black’;
}
allow you to use an event listener? It doesn’t make sense to me…
May 4th, 2006 at 2:07 am
very off topics.
Your getElementsByClass use i and j as global variable,
so if you use in a multithread js you could have weird behaviour.
to return in topics, I think that this method have the same problem because some function running in multithread could overwrite myElement['foo'].
I think you were refer to this when you said
“just in case you’re wondering - yes - that can be overridden, but chances are, it won’t”
.:sorry for my english:.
May 4th, 2006 at 11:33 am
Kentaromiura, thanks for pointing out the global vars in the getElementsByClass. That was a big oversite that normally I wouldn’t do. And to think, you’re the first to have pointed it out. You’re english is excused ’round here ;)
May 5th, 2006 at 3:41 am
Slightly off topic
I was going through the source of the demo page and found that you were referring the Yahoo UI library hosted at some Yahoo server. I was just wondering whether Yahoo allows us to use the hosted libraries. My understanding was that we have to host these libraries on our own servers. Can you clarify?
May 5th, 2006 at 4:28 am
@Sudar,
According to the Yahoo! Developer site, you do have to host the files yourself:
But you have to remember for whom Dustin works… ;)
(P.S. - he works for Yahoo!)
May 5th, 2006 at 4:31 am
Dustin,
This post was a great help to me. I had it in my mind that some of these scoping issues were a weakness in the language, and these are the issues that were killing me in my ‘generic AJAX library’.
Now that I have seen your original ideas, and read up on call and apply, I am trilled to see the power of JS revealed once again! Now I can get rid of some of the clunky eval I have in my AJAX code.
–Jim
June 20th, 2006 at 6:01 am
How would this work for multiple elements? I would like to attach onclick events to each link in my document but I’m failing to see how the use of the intermediate() function can help me achieve this as it has the element ID hardcoded into it.
Thanks.
June 20th, 2006 at 9:16 am
Hi Russ,
I’m not sure if you finished the entire article… did you make it to the end where call and apply are used? If so, you can definitely use something like Dom.batch. That’s turned out to be one of my favorite functions of YUI.
June 21st, 2006 at 2:46 am
Sorry I should have clarified my intent. This script adds event handlers to a number of different elements, allowing the user to interact with a single element on the page (document.getElementById(”blue”) in the intermediate() function).
But what if you wanted each element to target different element? i.e. when you click on a link element, you want to make its parent black instead of the element with the id “blue”? Does that make sense?
June 21st, 2006 at 9:21 am
Hi Russ, See this article as it will give you a good idea on how to use the event utility which allows you to pass in arbitrary arguments into the addEvent function. Eg: (click this, send that methodology).
August 2nd, 2008 at 1:05 pm
Dustin,
another fabulous solution I found here:
http://www.kevlindev.com/blog/?p=72
this does not need the global scope and works great