Scope This Out

Oct. 16, 2019


This blog post is a short one with the only objective being to provide a simple example of JavaScript lexical scoping. Lexical scoping is a fundamental concept of the JavaScript design and implementation, but many (self-professed) developers don't really have a fundamental understanding of how lexical scoping works. This can lead to a lot of frustration trying to debug code if one doesn't have a fundamental grasp of the JavaScript language. Of course, this applies to any language or technology but we will focus on JavaScript. I assume most, if not all, JavaScript programmers know that JavaScript is a lexically scoped language, but many struggle to explain what that means. In plain English, lexically scoped simply means the existing variable environment at the time of a function object's creation is available to it throughout its lifetime. This is known as a closure. The Mozilla web docs explain closures this way: A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time. This means a closure has access to three scopes; the local scope, the outer function(s) scope, and the global scope. This allows JavaScript to emulate object oriented techniques such as associating data with one or more methods (functions) while keeping that data hidden from other JavaScript code.

A simple example will help illustrate the critical concepts necessary to understand lexical scoping and closures. Say we want to have four buttons and a counter that tracks every time a button is pressed as well as the total number of clicks or "pushes." Below is some JavaScript code that allows us to do that:

function displayMsg() {
    total = 0
    num = [0,0,0,0,0]
    for (let i = 1; i < 6; i++) {
        $("#btn" + i).click(() => {
            num[i]++
            total++
            $("#p" + i).text(i + " has been pushed " + num[i] + " times")
            $("#total").text("Total Pushes: " + total)
        });
    }
}

function showMessage(i) {
    $("#btn" + i).click(() => $("#p" + i).text("You pushed " + i));
}
function displayMsgAnon() {
    for (var i = 1; i < 6; i++) {
        (function() {
            showMessage(i);
        })();
    }
}

displayMsg()
//displayMsgAnon()

When I was a young lad (many, many years ago) there was a saying "sticks and stones may break my bones, but words will never hurt me." I'm not sure if that's still a common phrase since that might be viewed as violating someone's "safe space," committing a "micro-aggression," or anything else that can send today's super sensitive sorts into a meltdown. Regardless, in JavaScript the words you use can definitely hurt you so keep that in mind! Let's talk about let vs. var. Both words have three letters, each consists of a consonant, vowel, consonant pattern, and on the surface they seem to do the same thing. However, the let keyword, introduced in ECMAScript 2015 is significantly different from var. Using let can help avoid problems with closures because let binds block-scoped variables. Look at the displayMsg function above. If we were to replace let with var then we would create 5 closures, but they would all share the same lexical environment because the scope of var is at the function level. This means that only the last closure would be maintained (i.e. for id = btn5 which is not even defined in this code!). The use of let binds fresh values because the scope of let is at the block (vs function) level. Until ES15 the general workaround was to use anonymous functions which create a new lexical environment for each callback. An example of this approach is demonstrated by displayMsgAnon which immediately executes and creates a callback for the value of i passed to the showMessage function. This example doesn't use the displayMsgAnon function (commented out), but it is included as an example of how to use var and anonymous functions to create the same behavior. Feel free to push the buttons below:

You can readily see that pushing one of the blue buttons updates the number of times a particular button has been pressed and the total number of all button clicks. This page contains HTML and embedded JavaScript (JS shown above) and the displayMsg function is executed. This function defines two variables total and num and then executes a for loop to bind an event listener attachment (i.e. a function) based on the current value of the variable i. Each iteration creates a new lexical scope and the use of let binds the variables at the block level meaning each closure has access to the variables total and num. Thus, each time a particular button is pushed the value of i is the same for a given button and the variables defined within the block can be accessed and manipulated. Note: This code uses JQuery to manipulate the DOM and the function passed into the click function uses arrow function syntax which was also introduced in ES15.

This simple example should give readers a good understanding about some fundamental JavaScript concepts including lexical scope, closures, and the difference between the let and var keywords.

Comment Enter a new comment: