Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with:

Using JavaScript's With Keyword To Create A Dynamic Scope Chain For Method Execution

Posted by Ben Nadel

So last night, I was laying in bed fantasizing about JavaScript - as I often do - when I started to think about using JavaScript's With keyword in conjunction with function definitions. In the past, I've played around with function execution in the context of a With-augmented scope chain; but, I wasn't sure what would happen if a function was defined in the context of a with-augmented scope chain. Would the function closure maintain reference to the augmented lexical scope chain? Or would it simply bind to the local scope of the parent function?


 
 
 

 
  
 
 
 

To explore this concept, I created a function factory that would defined a function in the With-based context of a "scope" object. This scope object would then be stored in the function itself as a property value. I did this to see if mutating the stored scope reference at runtime would alter the variable lookup within the function itself.

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Creating A Dynamic Scope Chain For Method Execution</title>
  • <script type="text/javascript">
  •  
  •  
  • // I return a function with a "scope" property that can be
  • // used to alter the runtime bindings of the functions.
  • var getFoo = (function(){
  •  
  • // Create the scope for our function.
  • var scope = {};
  •  
  • // Add the scope to the lookup-chain using the WITH
  • // keyword. This will add "scope" to the chain of
  • // memory spaces in which Javascript will try to
  • // locate property values.
  • with( scope ){
  •  
  • // Declare our closure within the WITH-based scope
  • // chaing.
  • var getFooMethod = function(){
  •  
  • // Return "foo";
  • return( foo );
  •  
  • };
  •  
  • }
  •  
  • // Add scope as a property to the method as well so
  • // that it can be access and mutated at runtime.
  • getFooMethod.scope = scope;
  •  
  • // Return the method.
  • return( getFooMethod );
  •  
  • })();
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Store foo in the global name space.
  • this.foo = "Foo In Global Scope.";
  •  
  • // Get closest-scoped foo value.
  • console.log( getFoo() );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Store foo in the WITH-based function scope.
  • getFoo.scope.foo = "Foo In WITH-Based Scope.";
  •  
  • // Get closest-scoped foo value.
  • console.log( getFoo() );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Update the foo in global scope.
  • window.foo = "UPDATED Foo in Global Scope.";
  •  
  • // Delete the foo from the WITH-based scope.
  • delete( getFoo.scope.foo );
  •  
  • // Get closest-scoped foo value.
  • console.log( getFoo() );
  •  
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Creating A Dynamic Scope Chain For Method Execution
  • </h1>
  •  
  • </body>
  • </html>

As you can see, I am creating a function - getFooMethod() - in the context of "scope". Then, I am storing the "scope" reference back into the getFooMethod() function and I am returning it to the calling context. Once the calling context has the function reference, I try to execute it with various combinations of global variables. This is all to see what "foo" reference the getFooMethod() execution will find.

When we run the above code, we get the following console output:

Foo In Global Scope.
Foo In WITH-Based Scope.
UPDATED Foo in Global Scope.

This is actually pretty cool! As you can see, the getFooMethod() continues to be bound to the With-based, augmented scope chain even after it is defined and returned to the calling context. This allows us to alter its variable lookup chain at runtime.

This is very different than using call() or apply() which only change the this-binding of the function execution. This is also very different than using private variable references within the function as those do not allow for public augmentation; nor, do they allow for runtime fall-through to the global scope.

I am not sure how, or even if I would use this kind of functionality. Right now, the thrill is just in the technical details of the execution - not in its practicality. It's good to know, however, that a function closure includes With-based augmentations in its lexical binding.




Reader Comments

A nested function is redefined every time the outer function is called. That's what makes currying work as expected. So every time you called "console.log(getFoo());", you were defining getFooMethod based on the currently defined foo variables.

You're right, cool!

Reply to this Comment

@WebManWalking,

I think you're actually missing a tiny bit of the syntax. If I remove all of the inner logic, we get this:

  • var getFoo = (function(){ /* ... */ })();

Notice the "self-executing" syntax at the end. The getFoo() method is only getting created once and returned (and stored in the getFoo variable). It's only getting defined once.

Reply to this Comment

Right you are.

I think what messed me up was seeing this.foo (rather than window.foo) right after the self-executing part. Unconsciously I must have thought that you were still defining getFooMethod or something.

So the entire context of a nested function definition is saved with the "closure scope" of the nested function. The with is part of the entire context, so it's still in effect when getFoo is executed.

I guess it makes sense from the point of view of lazy evaluation. Resolving the with at definition time would be too eager. For lazy evaluation to work, the with has to be preserved with everything else so that it can be evaluated only on need.

I first realized that JavaScript used lazy evaluation when I realized that funcationname.call is also a function, so therefore functionname.call.call must also be defined. That kind of completeness REQUIRES lazy evaluation, or else the very first function definition would result in an infinite loop!

Reply to this Comment

@WebManWalking,

Yeah, I think I went with "this.foo" just to mix it up a bit - I had it unscoped at first (var foo). I just wanted to play around with a variety of approaches to make sure something funky wasn't happening with references.

When we get into the world of Function.call.call.call.call ... my head starts hurt :D

Reply to this Comment

@WebManWalking

Property references are resolved at runtime, because the runtime must walk the prototype chain.

See http://mckoss.com/jscript/object.htm

Local variable references are generally resolved at compile time, *except* when in the presence of a `with` block.

@Ben

The `with` keyword of JavaScript, much like the `goto` keyword of C, is generally considered to be harmful for the vast majority of cases.

So long as the `with` keyword is not present in a JavaScript program, the compiler is able to link the definitions of local variables with all uses of the local variables statically (at compile time). The compiler includes such examples as Google's V8, which compiles and executes JavaScript, and Google's Closure Compiler, which compresses JavaScript source code into code which is semantically equivalent. When the `with` keyword is introduced, the compiler can often not figure out what's going on - and neither can a human. In this respect, the `with` keyword is like the `eval` keyword: they make life hard for everyone.

- Justice

Reply to this Comment

@Justice: That's what lazy evaluation, or evaluation on need, means. But what's really going to bake your noodle later on is, would you still have considered 'with' broken if Crockford hadn't said anything?

Reply to this Comment

@WebManWalking,

I haven't ever used it because I haven't ever liked it. Crockford put forward an explanation of why it's just not a good feature. But the stuff about the compilers and minifiers comes from following the compilers and minifiers.

- Justice

Reply to this Comment

@WebManWalking,

Runtime resolution is not equivalent to lazy evaluation. In fact, most languages that feature runtime resolution nevertheless default to strict (non-lazy) evaluation. By contrast, the language Haskell features static resolution but lazy evaluation.

- Justice

Reply to this Comment

Crockford said with was bad, so let's do the exact same thing with catch!

  • try{
  • new Throw()
  • } catch ( scope ){
  • //Woohoo!
  • }

Reply to this Comment

Just stumbled upon this one on Google javascript style guide.

Using "with" clouds the semantics of your program. Because the object of the with can have properties that collide with local variables, it can drastically change the meaning of your program. For example, what does this do?

with (foo) {
var x = 3;
return x;
}
Answer: anything. The local variable x could be clobbered by a property of foo and perhaps it even has a setter, in which case assigning 3 could cause lots of other code to execute. Don't use with.

Reply to this Comment

@Ben, as you know, I've often plugged the GoogleTechTalks YouTube video of Nicholas Zakas "Speed Up Your JavaScript" for its description of how 'with' works:

http://www.youtube.com/watch?v=mHtdZgou0qU

This article (yours, here) and the comments made me feel fuzzy on it again, so I went to the Zakas book High Performance JavaScript, Chapter 2 - Data Access. He went into much deeper detail in the book than in the video, so it's starting to come into focus again.

There are several scope chains in your example above. The outer anonymous function had one, while it existed. Then, when it was self-executed by (), this created a temporary execution-context scope chain, which initially started out as a copy of the anonymous function's scope chain. It's this second chain that got dynamically augmented by the 'with'.

Then you created the nested function, which defined its own scope chain at that time which included the 'with' of the outer function's execution context. Then the right brace of the 'with' closed it, ending its dynamic augmentation of the outer function's execution context scope chain. But that didn't affect the inner function. The inner function had 'with' in its scope chain at the time it was created, so its scope chain still contains the 'with', even after the 'with' ended.

Later, when you executed getFoo, the saved copy of the inner function, each execution created an execution-context scope chain that started out as a copy of the one when it was originally defined (both copies containing 'with'). These getFoo() scope chains did not change. Each getFoo() execution always had the 'with' in its scope chain.

So the second getFoo() displayed Foo In WITH-Based Scope. because window.foo was "shadowed" by the foo of the 'with', not because the variable lookup chain was altered at runtime.

I guess you could say "six of one, half a dozen of the other". But the take-away from Chapter 2 was that there's an unchanging scope chain snapshotted at function definition time and a bunch of execution-context scope chains created by duplicating the unchanging snapshot.

I'm not sure whether I like myself or hate myself for needing to know stuff at this level of detail. :-) But thanks for making me think about it.

Reply to this Comment

If any of you have followed my ColdFusion code, you know I am a super fan of scoping in general. In ColdFusion, unless I'm creating a variable that is used in that CFM file and ONLY that CFM file, I'll leave it unscoped; but pretty much every other reference in my code is scoped to something (Form, Url, Request, [query], Local, etc.).

I like scoping :)

At the end of the day, I wouldn't actually use the "with" keywords, except for perhaps with templating. I just like to see how the language can be twisted a bit - see how it works.

In the Crockford book, he does talk about the perils of "with". I think the way he showed it, one expression could result in like 4 different execution branches depending on the existing structure of the with'd scope.

This is all just in good fun.

@Justice,

It's funny you bring up GoTo. I first saw that in qBasic - my first language in High School. When I saw it, I was like, "Whoa, that is badass!!!". I told my friend - who was already in the Pascal class - and he told me that GoTo is total garbage. He said that once I learned about IF and CASE statements and functions (and sub-routines), I'll never think about GoTo again.... and he was right.

Oh the memories.

Reply to this Comment

Funny. I used the equivalent of "goto" all the time in my first programming class in college.

Of course with assembly language you don't have much choice ;-P

Reply to this Comment

@Matt,

Ha ha, oh man, Assembly Language brings up sort of terrifying memories :) I took that one semester in school. The professor had us doing exercises where we had to try and remove as many NOOP commands as we could from our code. I had no idea what I was doing :)

Reply to this Comment

so basically the "with" block creates a second execution context and then adds the implementation of getFooMethod which returns foo. does that sound right? so like after the call to getFoo() there is two executing contexts? one for the normal closure of returning the function and another from the "with" block? to totally solidify the concept it would be cool to see a little diagram showing the chain and executing contexts. I think that would help lots of your people in understanding the behind the scenes action. btw, cool investigation!

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.