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 »
11,238 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
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
May 14, 2013 at 6:57 PM
The UX Of Prototyping: Low-Fidelity Is The New High-Fidelity
@Phillip, I'm not sure I follow what you mean? Are you saying that you looked at the list of widgets provided by the jQuery UI and let that be your style guide? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools