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 thismakeBlack 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 ourmakeBlack 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 themakeBlack 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 tofire with scopecan 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 arecall 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.
recent
- Matador: The Obvious MVC Framework for Node
- Sandboxing JavaScript
- Crouching Ender, hidden command
- Ender.js - The open submodule library
- Qwery - The Tiny Selector Engine
- Klass
- Smallest DOMReady code, ever.
- $script.js - Another JavaScript loader
- About that slowness on Twitter...
- Autocomplete Fuzzy Matching
- JavaScript Cache Provider
- JavaScript Animate
- Asynchronous method queue chaining in JavaScript
- Something changed
- Unofficial Twitter Widget Documentation
i am dustin diaz

