Context and Scope in JavaScript, and Other Related Concepts

Sergio Valverde
8 min readDec 18, 2017

--

I intend this article to be part of a series, where I write about fundamental web development topics that are critical for a well-rounded knowledge of front end web development. Although sometimes simple, these are concepts that must be well understood in order to really be proficient as a JavaScript developer.

As a JavaScript developer, can you clearly explain the difference between context and scope? What about hoisting, closures, and the possible different values for “this”?

Two terms in JavaScript development that are often confused with each other are context and scope. They are also part of the fundamentals of the language, that any serious JavaScript developer should seek to understand.

Basically, scope refers to the visibility of variables; it defines in what parts of the application they can be accessed, depending on where they were defined. Context, on the other hand, relates to the object within which a function is called, and is referenced by the “this” keyword. The value that “this” references (a.k.a, the context) changes depending on how a function is called. We’ll cover both topics and some other important related concepts in this article.

Scope

As mentioned before, scope refers to the accessibility of variables from different parts of a JavaScript application. JavaScript has function scope (at least pre-ES6, see more below), meaning that any variables declared inside a function will be available inside that function, but not outside of it. Also any child functions declared inside of the external function will have access to the variables from the parent function.

In this and the following examples I use an immediately invoked function. Here the variable x is scoped to just the function: it is undefined outside of it. Note: For all examples I use an inline evaluation tool called Quokka, which evaluates the code right on the editor.

JavaScript's function scope is in contrast with other programming languages, which have block scope. A block is defined by a pair of curly braces { } , and having block scope allows you to create temporary variables inside structures like if or for loops, which will be inaccessible from outside this block.

This was not the case in JavaScript: When you declare a variable with the “var” keyword, it will be scoped to the whole function where it’s defined, regardless of whether it was declared inside any kind of block structure. If you don’t properly understand this, it can lead to bugs where variables that were meant to be used only inside a block are available outside of it.

Notice how here the variable i is still defined after the block where it was defined, and it holds the value 10, because its scope is the whole function, not just the “for” block.

A variable declared using var will be available anywhere inside that function, even in lines before the actual declaration (but still inside the same function). This happens because of a feature called hoisting, which means that the variable declaration is moved to the top of the scope (the function where it was declared). Only the declaration is moved, the assignment stays in place.

CAUTION! If you forget to declare a variable using var, it will be declared as a global variable! Javascript will look for the declaration up the function chain, to see if the declaration was in an outer function, finally reaching the global scope and declaring the variable in the global scope.

This example is the same as the first one, but without the var keyword. The x variable is still available after the function because x was hoisted to the global scope.

Starting from the sixth version of ECMAScript (the official specification for the JavaScript language), published in 2015, the language now includes two new keywords to declare variables: let and const, which are block-scoped instead of function-scoped. In general, it is recommended to use let instead of var, since they work the same except for the scope, and having block scope for variables is less error prone.

This is the same as the previous “for” loop example, but now using let instead of var. Note how the i variable is only defined inside the block now.

Const is a bit special, because variables (or should we say, constants) declared with the const keyword cannot be reassigned. If you declare a const and assign it a primitive type (a number, a string, a boolean, null or undefined), this value cannot be changed in any way. If you declare a const that references an object however, it doesn’t cause complete immutability, because the properties of the object can still be changed, but you are prevented from reassigning another object to that constant.

In these two examples, the first one throws an error because we are trying to reassign a const variable. The second one doesn’t throw an error, because we are only modifying a property of the const object, we are not actually reassigning the object it references.

Again, it is recommended to always use const over let and var whenever you can, which is actually in most cases. Using const variables is a good practice because it minimizes mutable state: It minimizes the amount of variables that can change inside of the logic of an application. The more moving parts (for example, the internal state that can be modified) that a piece of software has, the more likely it is to have unexpected bugs. If you declare a variable as a const, you can have the confidence that it will not be changed later on in the program. This is also beneficial in terms of readability and maintainability, because if another programmer or even you have to go back to the code in the future, it will be clear that the intention of that const variable was to remain constant.

Closures

Closures are also a fundamental aspect of the topic of scope in Javascript, that must be understood well. There are a lot of good resources explaining closures, like Eric Elliott’s article “Master the JavaScript Interview: What is a Closure?”. I recommend reading his article for a more detailed explanation. He defines closures as:

“A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).”

This means that when you have an inner function inside another function, the inner function will have access to the scope of the outer function, including the arguments received by the outer function, and will continue to have access even after the outer function has executed and returned.

Closures in JavaScript are important to implement design patterns such as the module pattern or revealing module pattern, which provide data privacy to objects and functions. This article will not go into details of design patterns, but basically closures make this possible because the inner functions can have access to a scope that will be completely inaccessible to anything else. Closures are also used for functional programming techniques like currying and partial application.

Here, greeter is assigned the return value of the immediately invoked function, which is an object with a sayHi method. This method has access to the variable greeting, even after the outer function finished executing.

Context

Context in JavaScript doesn’t refer at all to variables and their visibility, context refers to the object within which a function is executed, and it’s referenced by the “this” keyword, which allows you to access its properties or methods. Depending on how a function is called, there are six possible scenarios, where “this” will take different values:

  1. Outside of any function: If the keyword “this” is used outside of any function, it will refer to the global context. In browsers, it would reference the window object.
  2. Inside a constructor function: When a function is called as a constructor, using the new keyword, then any usage of “this” inside of the function will refer to the new object instance that will be created. In this way constructors can add properties and methods that will belong to the new instance (not including adding shared methods to the constructor’s prototype, but that is another topic).
  3. Inside an object method: An object method is a function that is a property on an object. In this case, “this” will be a reference to that object. This is very often used to access and modify other properties or methods of the object.
  4. Inside a simple function: This means a function that is just declared as a “stand-alone” function, without being a property of any object, and without calling it as a constructor with new. In these cases, “this” will reference the global context (window object in browsers).
  5. Inside a function that is called as a callback for an event listener: Regardless of where and how a function is defined, if it is called as a callback for an event listener (for example, to be called when a click event is triggered), “this” inside that function will reference the target element of the event (not counting arrow functions). This is a very common situation in JavaScript, and a frequent source of errors, because the “this” value that would probably make more sense to a developer is overridden by the target element of the event. There are several ways to get around this issue, that I’ll mention below.
  6. Inside an arrow function: In ES6 arrow functions were introduced, and not only do they provide a more concise syntax for writing functions, but they also have the peculiarity that they will retain the same context as its lexical environment (the context of the function's surroundings at the time of its creation), no matter how they are called later. Using arrow functions is actually one of the ways to deal with the issue of event handlers changing the context of callback functions.

Bind, Call, Apply, and other ways to manually specify context

Now that we saw how context is normally determined, it’s also important to mention some techniques that can be used to have more control on the context of a function.

  • Arrow Functions: Even though arrow functions can really be considered as one of the six common scenarios that determine context, it is anyways worth noting again that they are a useful technique to use when you want to prevent the context of a function from being changed, such as when calling a function as a callback for an event handler. This way, you know that the function will have the same context from where it was defined.
In this example, we create a new test instance from a constructor function. Since the context inside constructor functions is the new instance, we would expect the method logThis() to log the instance, but since we are calling it as a callback to a click event on a button, “this” becomes the button.
However, note what happens if we just change the declaration of logThis to be an arrow function: Even when using it as an event callback, it will correctly print to the console the instance created. This happens because the arrow function’s context will be permanently set to the context of its surroundings at the time of the function’s creation.
  • Bind: The function bind is available as a method of any JavaScript function (thanks to prototypal inheritance, since bind is a method of Function.prototype). When calling bind on a function, it will return a new version of the function, that will have the context permanently set to whatever object bind receives as its first argument. Bind can also optionally receive more arguments, which will then always be used as arguments for the target function whenever it’s called. This new function can then be safely used as an event handler’s callback, knowing that the context will remain fixed.
In this version of the same example, we are back to using a regular function for logThis, which would print the button element instead of the instance when clicking on the button. However, we are preventing this here by using a callback for the click event that is bound to the test instance, therefore its context will not be changed.
  • Call: This function is similar to bind, and can also be called on any function. The difference is that call doesn’t return a new version of the target function, it just immediately executes the target function while setting its context to be the object that call() received as an argument. As with bind, call can also receive extra optional parameters, that will be passed as parameters for calling the target function.
In this example we create two dogs, Rufus and Duke, and then we call the method getName of Rufus on line 11. As expected, it returns “Rufus”. However, if we then call the same method of Rufus with .call(Duke), it will treat Duke as its context, overriding the normal context, even though it still is a method that belongs to Rufus.
  • Apply: Apply works exactly the same as call, except that the optional parameters should not be passed individually, but as an array of values. For our purposes, it would be used in the same way as the previous example for call().

--

--