Trying To Mimic LET Functionality In JavaScript Using Self-Executing Functions

Posted January 17, 2012 at 10:31 AM by Ben Nadel

Tags: Javascript / DHTML

Last night, I was listening to the Lately In JavaScript podcast. Among the many items discussed, one topic that was brought up was the emergence of the LET keyword in the next version of ECMAScript (the language on which JavaScript is based). LET allows for block-level variable binding; so, while the VAR keyword allows for function-level variable binding, LET allows variables to be defined for a smaller scope of code execution. LET-like functionality can be achieved through the use of closures; and, while I've talked about it before, when exploring closures, I thought it might be nice to demonstrate this as a stand-alone post.


 
 
 

 
  
 
 
 

As one of the earliest posts on my blog, way back in 2006, I talked about the problem with variable binding inside the context of loops. If you didn't understand variable hoisting (as I didn't at the time), configuring event handlers inside a FOR loop could quickly lead to unexpected and frustrating behaviors.

When I started using jQuery, shortly thereafter, I was super pumped-up to find out that the .each() method could alleviate some of these variable hoisting problems. By using a callback for each "loop iteration," jQuery's each() method bypassed the hoisting problem by providing each iteration with its own scope (and hoisting context).

This same sandboxing effect can be used outside of jQuery through the use of self-executing function expressions. Essentially, you can create an anonymous function within the block of a FOR loop; then, for each iteration, invoke the anonymous function, defining the relevant context and passing in the index values.

In the following demo, we'll define a number of methods that bind to the "current" value of "i." Then, we'll execute those methods after the loop to see if the iteration-specific value-binding took place.

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Inline Functions Create Sandboxes For Variable Binding</title>
  • <script type="text/javascript">
  •  
  •  
  • // Create a buffer to hold our function - these will invoked
  • // at the end to demonstrate variable binding.
  • var methodBuffer = [];
  •  
  •  
  • // We'll create 5 functions.
  • for (var i = 0 ; i < 5 ; i++){
  •  
  • // Create a "unexpected behavior" binding to i.
  • var j = i;
  •  
  • // Create an inline, self-executing function to create a
  • // closure and sandbox for the execution of this loop
  • // iteration.
  • (function( i ){
  •  
  • // Create our testing function - notice that the
  • // console.log() method is referencing "i", which is
  • // a variable defined by our self-executing function,
  • // and the variable, "j", which is a variable defined
  • // in the FOR loop block.
  • var method = function(){
  • console.log( i, "-", j );
  • };
  •  
  • // Add it to the buffer - we'll see if the binding
  • // holds up afterward.
  • methodBuffer.push( method );
  •  
  • }).call( this, i );
  • // Notice that when we invoke this inline function,
  • // we're binding the execution context AND passing in
  • // the current value of [i].
  •  
  • }
  •  
  •  
  • // Now, test the methods and the variable bindinds.
  • while (methodBuffer.length){
  •  
  • // Get the front method (changes the length of the array).
  • var method = methodBuffer.shift();
  •  
  • // Invoke it to see if the "console.log(i)" variable
  • // binding is referencing the correct value.
  • method();
  •  
  • }
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

As you can see, we have a simple FOR loop iterating from zero to four. For each loop, we define and then invoke an anonymous function block. In order to maintain the proper execution context, we're using the call() method to explicitly set the "this" value used within the anonymous function. As part of the anonymous function invocation, we're also passing in i - the value of the loop iteration index. This creates an iteration sandbox while also binding the iteration index to the invocation parameter of the self-executing function.

As a control, we're also logging the value of, "j," which is bound to the value, "i," at the FOR-loop level. Since "j" is not being passed into the self-executing function, it will fall victim to the hoisting effect. And, in fact, when we run the above code, we get the following console output:

0 - 4
1 - 4
2 - 4
3 - 4
4 - 4

As you can see, each function definition was bound to the appropriate value of "i." And, the value of "j" was bound to the last known value of "i".

The LET keyword can do a bit more than what I described; but, the primary function of the LET keyword can be somewhat replicated through the use of self-executing function blocks. Since the self-executing function blocks are invoked for each iteration of our FOR loop, it provides both an execution sandbox and an opportunity to bind iteration variables to invocation arguments.




Reader Comments

Jan 17, 2012 at 2:23 PM // reply »
1 Comments

Great post Ben, but I think that using

  • Function.prototype.call

may mislead readers, it isn't the key point in your post.

Moreover, the sentence "we're binding the execution context" isn't true. The "Execution Context" is defined in ES specification section 10.3 (see http://es5.github.com/#x10.3).

With

  • (function(i){ ... ).call( this, i )

you are binding the inner *this* and the *thisValue* of the enclosing lexical scope.

I think

  • (function(innerIndex){ ... ))(outerIndex)

is clearer for the point you're explaining.


Jan 17, 2012 at 2:30 PM // reply »
10,743 Comments

@Joseanpg,

You make a really good point regarding the use of call(). I was, originally, gonna just invoke the function normally; but then, after it was written, I went back and added the call() usage. Probably did more to dilute than clarify.

On that topic, however, I think I heard that one of the things they are going to "fix" in the upcoming release of ECMAScript is the way the "this" scope works in function expressions. Don't quote me on this, but I think they are going to do what we all *hoped* it would do - keep the same this-binding as the defining context (unless called via call() or apply(), of course).

From that link you provided, it looks like "Execution context" is composed of a number of things. As you pointed out, I am referring to the "this" binding; but it looks like that is only *part* of the execution context:

- Lexical environment (ie. closure'd variabled)
- Variable environment (ie. variable defs)
- This binding (ie. what "this" refers to).

That's a much better definition than what I had in my head. With all the scope chain stuff and stacks, I never really knew what to call it. Thanks for helping me get my mind straight!


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
May 21, 2012 at 1:58 AM
Updated: Converting A ColdFusion Query To CSV Using QueryToCSV()
Hi Ben, why do you need to have so many double quotes when adding the field and field name to the row data? ----------------------------------------- <cfset LOCAL.RowData[ LOCAL.ColumnIndex ] = ... read »
AXL
May 21, 2012 at 1:24 AM
URL Rewriting And ColdFusion's WriteToBrowser Image Functionality (CFFileServlet)
@Mounir, Open your lower case URL Rewrite rule and add the following condition. Condition input: {REQUEST_URI} Check if input string: Does Not Match the Pattern Pattern: ^/CFFileServlet/_cf_ca ... read »
May 20, 2012 at 4:28 AM
Understanding The Complex And Circular Relationships Between Objects In JavaScript
@Will Vaughn I tried your javascript example but got this error:- foo.print is not a function ... read »
May 19, 2012 at 5:37 AM
A Graphical Explanation Of Javascript Closures In A jQuery Context
Thanks for this article, but I fear you missed an important point. If variables in the outer context change, these changes affect the inner anonymous functions as well. That means: if you change the ... read »
May 18, 2012 at 3:39 PM
Parsing CSV Data With An Input Stream And A Finite State Machine
Can you use file upload button with this? and read live? or does the file have to already be on the server saved? ... read »
May 18, 2012 at 1:06 AM
VIRGO (Aug. 23-Sept. 22): Dead On The Money!
A friend of mine and I were arguing about astrology and she told me that he believes in astrology. She hasn't provided me with any evidence that the belief makes any sense to me. She she been telling ... read »
May 17, 2012 at 11:32 PM
Using ColdFusion to Handle 404 Errors (Page Not Found) On Development Server
Very easy the configuration. I read a lot pages and I can't find the solution. I open the administrator and change this Administrator/server settings/Error Handlers/Missing Template Handler and p ... read »
May 17, 2012 at 3:13 PM
LOCAL Variables Scope Conflicts With ColdFusion Query of Queries
I never cease to be amazed that almost EVERY random CF issue I come across lands me on your site. Thank you for documenting your findings for the world. ... read »