JS Fundamentals: Basics of Scope

javascript
Coding JS is an art. As we continue in our JS Fundamentals series we’ll unlock layer by layer the basics to working confidently in the land of Javascript.

Mastering the concept of “scope” is an important step towards mastering writing efficient, easily maintained JavaScript. In this installation of the JS Fundamentals series, we’ll take a high-level look at how JavaScript’s scope works.

There are two levels of scope – “global” and “block.” Of the two, the easiest to explain is global. Global scope variables and functions can be accessed by any portion of the environment in which it’s been created. Examples of this are the window Object or the setTimeout Function in the browser, and Node.js’s require Function.

“Block” scope is a little more tricky – a block scope gets created when you write a Function or a try/catch statement. Unlike other C-based languages, blocks that are defined by braces ({ and }) do not create a new scope; at least, not until ECMAScript 6 is adopted.

Let’s look at a few practical examples.

var foo = “Global variable”;  function globalFunc() {   var bar = “Scoped variable”;    function scopedFunc() {     console.log(‘foo is: ‘ + foo);     console.log(‘bar is: ‘ + bar);   };   scopedFunc(); } globalFunc(); scopedFunc(); 

If you run this, the output that you’ll receive from the two function calls would be “foo is: Global variable”, “bar is: scoped variable”, and a ReferenceError because scopedFunc doesn’t exist in the global scope.

This enables us to write functions (and store variables) that are only relevant to the execution of that scope, which is useful for when you’re running repeated tasks that only need to happen when a specific function is being called, but you don’t necessarily need it to be available anywhere else. Additionally, leveraging scope in this way means that any large variables (if you’re doing some intense data processing like manipulating the points of a GeoJSON map, for example) they can be picked up by the garbage collector when the function’s task is complete, and not continue to sit in memory.

Since we have access to global variables inside of a scope, those variables can get reassigned. Take this example:

var reassignable = ‘I am in the global scope’;  function reassigner() {   reassignable = ‘I am in the block scope’; }   console.log(reassignable); // >> “I am in the global scope”  reassigner(); console.log(reassignable); // >> “I am in the block scope” 

This will return the string “I am in the block scope” in the JavaScript console. Of course, the caveat to this is that you need to be careful with naming your variables. If you reuse a global variable, you’ll overwrite it.

Why is this important?

Scope is crucial to understand when working in JavaScript. The examples we’ve seen so far have been simplistic. One of the most challenging aspects of JavaScript for new developers – the this keyword – is heavily impacted by the scope in which it is being called.

However, it’s bigger than just keeping your this keyword context straight (and that particular subject will be focused on in an upcoming post).

If you’re working on a project of any substantial size (more than some basic prototyping, or a very simple static site with a bit of JavaScript thrown in for some enhancements), chances are good that you’re working with external libraries like jQuery or Modernizr (or, perhaps, both). How do you keep these libraries from colliding with one another? If you have both jQuery and Underscore, for example, how do you ensure that their various each methods don’t get overwritten by the other library?

Technically, these libraries use what’s known as “namespacing,” which is the practice of applying a scope to the library that prevents that library’s methods and variables from overriding the methods and variables of the environment to which they’ve been added.

jQuery uses two – one as an alias of the other – names: jQuery and the more familiar $. Underscore, as you might expect, uses the _ character. Therefore the respective each methods would be $.each and _.each. This is possible through what we’ve been discussing with scoping.

If you look at the code for either library, you’ll see that both are wrapped in something that looks like this:

(function (args…) { })() 

This is what’s known as a closure (and, more specifically, an “Immediately Invoked Function Expression” or IIFE). Though we won’t dig in to closures right now, what we know from what’s been covered so far is that any variables defined within a function is constrained to that function. Therefore, the authors of Underscore can implement their each method inside of that closure and not have to worry about whether or not what they write will interfere with a similarly named function that other libraries may introduce.

Finally, it’s important to note that scope and context aren’t the same thing. Context is all about the specific situation where something is being run, and is most easily explored through the use of the this keyword. Scope is what helps define context.

Variables

As we covered already, variables defined in a scope belong to the scope in which they’re defined. If a new scope is defined within the parent scope, they’ll be accessible the same way that global variables are accessible inside of block scopes.

There’s a caveat to this: if you forget to include the var keyword, any variable you create in a scope is automatically added to the global scope:

var globalVar = 1;  function someFunc() {   var scopeVar = 2;   otherVar = globalVar + scopeVar; }  someFunc();  console.log(‘globalVar: ‘ + globalVar); // >> 1 console.log(‘otherVar: ‘ + otherVar); // >> 3 console.log(‘scopeVar: ‘ + scopeVar); // >> ReferenceError: scopeVar is not defined 

Wait, what was that try/catch thing you mentioned earlier?

The one other place that creates scope, at least in ECMAScript 5, is the try/catch block. This is typically used when you know that a portion of your program has potential to throw an error and you want to make sure that it’s properly handled. The catch portion of this is essentially a function that accepts one argument – the error Object – and it allows you to work with that:

try {   undefFunc(); } catch (err) {   console.log('Error:');   console.log(err);   console.log('--------------------'); }  console.log('Error:'); console.log(err); 

This will print out:

“Error:” [object Error] { ... } // depending on your logging environment, this will either be an object you can interact with or just an indication that an object was logged “--------------------" “Error:” ReferenceError: err is not defined 

Note that the err variable isn’t available in the global scope, while it is available in the catch block’s scope.

In Conclusion

Scope is an important concept to grasp when working with JavaScript (really, with any programming language). As Kyle Simpson points out in his You Don’t Know JavaScript: Scopes & Closures, it’s important to remember that JavaScript is actually not a dynamic language – it’s compiled. When a browser loads a JavaScript file, it offloads the contents of that file to its JavaScript engine, where it’s read, compiled, and executed. Because of this process, it’s important to understand how scopes function, Otherwise you’ll likely run in to unexpected behaviors and errors, especially once your application grows in complexity.

A few good resources (in addition to Kyle Simpson’s book) that help with understanding JavaScript Scope are: