Faking Context In Javascript's Function() Constructor

Posted May 19, 2010 at 10:52 AM by Ben Nadel

Tags: Javascript / DHTML

In my jQuery Template Markup Language (JTML) project, I needed a way to compile JTML templates down into Javascript functions such that they could be executed at any time in order to generate new HTML markup. This is a tricky problem because the JTML code makes references to non-scoped variables that have to be available in the rendering engine's context at the time that it executes. When I coded this project, the only solution that I could figure out at the time was to actually create a new Function() every time a template needed to be rendered. This, to some degree, defeats the purpose of compiling the rendering engine ahead of time. After thinking about this problem for a while, I wondered if I could leverage Javascript's apply() functionality to create dynamically-scoped, pre-compiled functions.

 
 
 
 
 
 
 
 
 
 

When you use the apply() or call() methods to change the execution context of a given function, all it really does is change the "this" reference; this has no effect on non-scoped variables, which will still be found by crawling up the function's closure chain. However, what if we had a function that checked for its own "this" context before it executed? If we created a function that appended this-scoped variables onto its local context before running, we should be able to dynamically change the available non-scoped variables simply by changing the function's context. That is exactly what I tried to do in the demo below:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Javascript Function() With Context</title>
  • <script type="text/javascript" src="jquery-1.4.2.js"></script>
  • <script type="text/javascript">
  •  
  • // I am a proxy for the Function() constructor that prepends
  • // code to copy the function's context variables into the
  • // function local scope so that they may dynamically changed
  • // at the time of execution.
  • function FunctionContext( sourceCode ){
  •  
  • // Call and return the Function() constructor. Notice
  • // that if the THIS is not Window, we are VAR'ing the
  • // context variables into the function local scope.
  • return(Function(
  • "if (this != window){" +
  • "for (var __i in this ){" +
  • "eval( 'var ' + __i + ' = this[ __i ];' );" +
  • "}" +
  • "}" +
  • sourceCode
  • ));
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Define a function that uses a variable that is not
  • // defined as part of the function source code.
  • var saySomething = FunctionContext(
  • "console.log( name + ', you\\\'re looking so hot!' );"
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Now, execute the saySomething() method in three different
  • // contexts, each of which changes the name.
  • saySomething.apply({
  • name: "Tricia"
  • });
  •  
  • saySomething.apply({
  • name: "Vicky"
  • });
  •  
  • saySomething.apply({
  • name: "Erika"
  • });
  •  
  • </script>
  • </head>
  • <body>
  • <!--- Intentionally left blank. --->
  • </body>
  • </html>

Here, I have created the function, FunctionContext(), which is essentially a proxy to Javascript's Function() constructor. All it does is prepend a bit of logic to the given source-code before it is passed off to the native Function() constructor. The extra logic checks the "this" context of the function; if the function's context is not the window object, indicating that it has been overridden with a call() or apply() method, all of the this-scoped values are var'd into the compiled function's local scope. In this way, any variable that was in the context at the time of execution is now available as a non-scoped value within the function's logic.

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

Tricia, you're looking so hot!
Vicky, you're looking so hot!
Erika, you're looking so hot!

As you can see, through the use of apply(), I am changing the context of the compiled function for each execution. And, since the first part of that execution copies this-scoped variables into the local scope, the unscoped variable, "name," never causes an error.

I needed a way to dynamically change the variables that were available at the time of a function execution. If I just used the eval() function, I lose the benefit of pre-compiled optimization. If I use just the Function() constructor, I can't really figure out a way to change the available variables without using named arguments (which I may not even know ahead of time). By using both approaches together, however, I think I have found a mostly-elegant way to change the set of available local variables at the time of function execution.




Reader Comments

May 19, 2010 at 5:23 PM // reply »
15 Comments

this is a little over my head but I'm subscribing to the entry to be privy to what the rest of your readers have to add.


May 20, 2010 at 8:57 AM // reply »
10,638 Comments

@David,

Here's a perhaps more straight-forward was to accomplish this:

http://www.bennadel.com/blog/1929-Using-The-WITH-Keyword-With-Javascript-s-Function-Constructor.htm


Jan 23, 2012 at 7:24 AM // reply »
1 Comments

Yes, interesting. But at the end it comes to question isn't it too expensive to use eval in such an extent. Probably it is possible to solve this using literal notation or exploating namespaces? I quess.


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 3, 2012 at 10:49 PM
How I Got Node.js Running On A Linux Micro Instance Using Amazon EC2
Wow this was really helpful! Only thing I would add is you need to update your .bash_profile after you edit the secure_path. This is what I did: $ . ~/.bash_profile Otherwise, NPM won't be found. ... read »
Feb 3, 2012 at 10:14 PM
Pushing Base64-Encoded Images Over HTML5 WebSockets With Pusher And ColdFusion
@Ben, Just wanted to let you know that pusher are soon to start limiting sizes on messages. This was the detail that came through in the Feb dispatch: "However, we will soon be limiting the s ... read »
Feb 3, 2012 at 5:05 PM
Regular Expressions Make CSV Parsing In ColdFusion So Much Easier (And Faster)
I tried using your RegEx in my C# program, but it was matching an extra empty-string at the end and so I would end up with an extra field that doesn't exist, so I changed it to this: (^|,)("(?: ... read »
Feb 3, 2012 at 3:47 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
Josh Cyr posted this on Twitter just a little bit ago. Thought it was appropriate. http://stackoverflow.com/questions/1619152/how-to-create-rest-urls-without-verbs/1619677#1619677 ... read »
Feb 3, 2012 at 2:28 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
@Michael, You definitely make a good point (and extra points for quoting movies - I love movies). When you use a return() statement to define the object's public API, it does provide a consistent a ... read »
Feb 3, 2012 at 2:04 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
To quote Jurassic Park: "Just because you can doesn't mean you should". I completely, utterly disagree with the thought that this is more readable. Consider the current module pattern: if ... read »
Feb 3, 2012 at 1:10 PM
REST API Design Rulebook By Mark Masse
@Jordan, Yeah, WRML was created by Mark Masse (author of the book). I also found it to be a bit convoluted. I suppose it is intended to allow the Client to be able to programmaticaly respond to cha ... read »
Feb 3, 2012 at 1:08 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
@Jason, To be honest, I don't have good answers for that kinds of stuff. And, to the point, that is specifically why I *really* liked the REST API Design Rulebook by Mark Masse - he just cuts throu ... read »