Using The WITH Keyword With Javascript's Function() Constructor
Yesterday, I blogged about using the eval() function in conjunction with Javascript's Function() constructor in order to copy context variables into the local scope of the executing function. This was meant to allow non-scoped variables, references within the compiled function, to change with each execution. In a conversation following the post, Pedro del Gallego suggested that I might try looking into Javascript's WITH keyword as a way to accomplish the same outcome without eval() statements.
The "with" keyword is a black-sheep within the Javascript language. It's one of those constructs that you learn to frown upon because everyone else does, although you might not always know why. I never got into the "with" statement because I'm pretty fanatical about scoping my variables when a scope is available (with the exception of window-scoped values). But, it looks like the "with" statement might just be the perfect solution for this kind of problem.
When an unscoped (unqualified) variable is used in Javascript, the Javascript interpreter finds the value of that variable by walking up the scope chain available to the execution context. The "with" statement takes a given object and pushes it onto the current scope chain for the duration of the with-block. Once the block statement has finished executing, the given object is popped off and the scope chain returns to its previous state.
In my previous blog post, I used the eval() function to "extend" the local scope with individual context variables; in the following demo, however, I'm going to use the "with" keyword to actually add the "this" context onto the scope chain of the function execution:
<!DOCTYPE HTML>
<html>
<head>
<title>Using The WITH Keyword With Function Constructor</title>
<script type="text/javascript">
// I compile the given source code down into a Function
// that is executed in the context of the THIS object.
function FunctionProxy( sourceCode ){
// When executing the Function constructor, we are going
// to wrap the source code in a WITH keyword block that
// allows the THIS context to extend the local scope of
// the function.
//
// NOTE: This works without a nested self-executing
// function. I put it in there simply because it makes me
// feel a little more comfortable with the use of the
// WITH keyword.
return(
Function(
"with (this){" +
"return(" +
"(function(){" + sourceCode + "})()" +
");" +
"};"
)
);
}
// -------------------------------------------------- //
// -------------------------------------------------- //
(function(){
// Create a function that makes references to variables
// that are not in its closure chain.
var sayHello = FunctionProxy(
"console.log( girlName + ', you in danger, girl!' );"
);
// Execute the function with a custom context. The WITH
// keyword in the function's proxied source code will
// be used to extend the local function scope with this
// custom context.
sayHello.apply({
girlName: "Molly"
});
})();
</script>
</head>
<body>
<!--- Intentionally left blank. --->
</body>
</html>
As you can see, I am taking the original source and wrapping it inside of a "with-block". I am then passing the "this" context reference to the "with" statement which will add it [this] to the function's scope chain. Then, as the function executes, any non-local, unqualified variables referenced within the source code will be searched for within the "this" scope.
Inside of the with-block, I am wrapping the original source code within a self-executing function block. This is not necessary and is something that I am doing purely for emotional reasons. By packaging the source code up in such a way, I feel like I am completely separating the execution of the source code from the scope-chain-extension afforded by the "with" statement. Again, not necessary, but emotionally satisfying.
When we run the above code, we get the following console output:
Molly, you in danger, girl!
As you can see, the context object applied to the sayHello() function by way of the apply() method was successfully used as one of the scopes in the unqualified-variable scope chain. Pretty awesome! I've never been a huge fan of the "with" keyword; but, a huge thanks to Pedro - this is a much more elegant solution than the one I had yesterday.
Want to use code from this post? Check out the license.
Reader Comments
If you look into the interiors of some of the templating stuff out there (I believe I saw it in jQuery.tmpl and I know Underscore.js does it), they do something very similar with `with` to get the templating to work.
http://github.com/documentcloud/underscore/blob/master/underscore.js around line 627.
@Brian,
Very cool - thanks for the tip. Looks like they are doing something very similar to what I was doing. I guess all the templating systems work very much the same.
with is one of the cool keyword gives the awesome functionality and gives a lot means as well, i have not much idea about javascript but i have used with keyword in many languages.
anyway thanks for sharing this good information with us.
http://www.usedtrucksdeal.com/
@Ben,
The first video at
http://www.smashingmagazine.com/2010/07/17/seven-must-see-videos-and-presentations-for-web-app-developers/
is Nicholas Zakas giving an awesome talk about speeding up JavaScript. At 5:21 into it, he begins to talk about "scope management". This is EXACTLY analogous to ColdFusion unscoped variable resolution.
As you know from Ben Forta's ColdFusion Certified Developer Exam Study Guide, there's an explicit sequence of scopes that CF uses to resolve an unscoped variable refernce. Well, turns out, JS does the same thing, except that the chain is variable length and based on execution context.
Start watching at 5:21, so that you get the meaning of his chain diagrams. Don't jump ahead to 9:39, where he begins talking about the effects of the with statement. Turns out that 'with (obj) { ... }' pushes a new context onto the front of the execution context stack. Between the braces, any name that matches a property of obj will be resolved to that property, and the scope resolution chain immediately terminates.
That's bad if you're trying to reference variables further down the chain, but it's good if you're mostly doing stuff obj's properties.
During the video, Nicholas Zakas references Douglas Crockford's "with Statement Considered Harmful" post in YUIBlog:
http://www.yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/
In that post, Crockford cites indeterminence as the reason: "There is no way that you can tell by looking at the code which bing and bang will get modifed. Will [the object's properties] be modified? Or will the global variables bing and bang get clobbered? It is impossible to know for sure."
Well that may have been true at the time of that YUIBlog post on 04/11/2006 (Alessandra Ambrosio's birthday, by the way). But ECMA has eliminated that indeterminence. Now the problem is lengthening the scope resolution chain, if what you're trying to reference is outside the obj scope.
I guess what I'm trying to say is, Papa Crockford may consider 'with' harmful, but I don't. Like sex, it's not a "bad thing". It's just a "be careful thing". Bad things can happen if you're not careful.
Yeah, don't needlessly lengthen the scope resolution chain. But if you're very focused on an object's properties, using 'with' could be used to speed things up.
My $0.02.
Augmenting the local scope of functions eluded me for months (with tons of searching) until tonight when I was re-reading John Resig's Micro-Templating post. http://ejohn.org/blog/javascript-micro-templating/
Specifically, his use of the
constructor made me realize I could simply create a closure around some
statements. Once I had a working version and a much better understanding, my first search brought me here. The
statement makes total sense, I didn't realize it also augments the scope chain. I've only seen it used to extend objects.
Anyway, I created a jsperf test on the subject and using a
statement is the fastest. It would be nice not to have to use
when calling the function, but trying to truly stringify an object in older browsers is just too messy.
Feel free to edit the test. http://jsperf.com/augmenting-the-scope-chain