DEV Community

Cover image for Tricky bits of JavaScript

Posted on

Tricky bits of JavaScript

Photo by artM from FreeImages

When learning a new language, it makes sense that some parts will seem kinda familiar while others are quite mysterious. I'm studying JavaScript now and there are a few tricky bits I'd like to review in more detail.

      var, let, const  ◆  hoisting  ◆  scope  ◆  closure

Before we go into the topics above, let's take a look into how JavaScript is working 'behind the scenes'. The JavaScript Engine actually makes two different passes over your code and in the first pass, called the Creation phase, the code is not executed line by line. Instead, a global object and an associated this are created, and variables get set up in memory. During the second pass is where the work begins, called the Execution phase because this is when your code is actually run line-by-line and values are set to the variables previously defined and stored in memory during the first pass.

Having a good understanding of how the JavaScript engine actually evaluates your code is a valuable tool. This will be more evident as we explore the topics mentioned inside the box at the top of this post.

Prior to the release of ES6, var was the only choice for variable declaration. As it turns out, var can cause some trouble in your code due to its flexible nature, and that is where let and const come to the rescue. Let's take a look at each variable declaration type a bit more closely.

Before we go over var, let, and const, it is important to review the concept of scope. Scope is defined as "the current context of execution" and is basically where a variable is available to use. In the case of var, it is function scoped when declared within a function and global scoped when declared outside a function. Function scoped means that the variable is accessible and available within that function only. This is quite different from global scoping where the variable becomes available to the entire window.
Consider this example:

   var mood = 'happy';

   function changeMood() {
     var newMood = 'curious';


Here, the first variable declaration is within the global scope, so it is available for use anywhere within the window. When you console.log(mood), the output is 'happy', accessed from the global scope.

The second variable declaration, inside the changeMood() function, exhibits functional scope behavior. When changeMood() is called, the output is 'curious' since the variable newMood was declared within the function. It is available to the function itself, but if you tried to console.log(newMood) from outside of the function, you get: ReferenceError: newMood is not defined. The scope of newMood is only within the changeMood() function.

In the case of let and const, variable declarations using these keywords are block scoped only. This means that variable declarations can only be accessed within the block (inside the curly brackets only) from which they were declared. Keeping variable declarations out of the global scope helps to prevent bugs and issues with overwriting variables previously declared.

Now that we've briefly covered scope, let's take a look at some of the behaviors of var that led to the need for let and const. When you use var to declare a variable, JavaScript won't prevent you from redeclaring that variable, in fact it won't even let you know that you have overwritten a previously declared variable. Oh no!

  var cat = 'Coco';
  var cat = 'Beebee';

  console.log(cat);  //=> Beebee

The output for cat is 'Beebee' since var allows for redeclaration of variables. This is an easy way to put bugs into your program and should be avoided. Take a look at how let and const prevent this down below:

  let cat = 'Coco';                  const cat = 'Coco';
  let cat = 'Beebee';                const cat = 'Beebee';

  console.log(cat);                  console.log(cat);

  `SyntaxError: Identifier 'cat' has already been declared`

Also note that var and let variables can be updated, whereas const can only be defined once and may not be updated.

Remember how the JavaScript engine processes your code in two passes? Good, let's put that knowledge to use. Hoisting is defined as when variables and function declarations are 'moved' to the top of scope before execution. Well, they aren't really physically moved, but during the Creation phase they are set into memory and variables declared with var are initialized with a value of undefined. Variables declared with let and const are not initialized with a value, but are still set into memory. So if you try to use a variable with let or const before it is declared, a Reference Error occurs. Let's take a look at some examples:

    console.log (greeting);
    var greeting = "hola"

Will be interpreted like this:

    var greeting;
    console.log (greeting); //=> greeting is undefined
    greeting = "hola"

And when using let or const:

  console.log (greeting);         console.log (greeting);
  let greeting = "hola";          const greeting = "hola";

ReferenceError: Cannot access 'greeting' before initialization

An interesting note about const:
Variables declared with const must be initialized at the time of declaration.

   const greeting;
   console.log (greeting);
   greeting = "hola";

   SyntaxError: Missing initializer in const declaration

So in summary:

Scope Update? Re-declare? Hoisted? Initialized
var global or functional yes yes yes undefined
let block yes no yes no
const block no no yes required

The last topic I would like to cover here is closure. Closure is when an inner function is able to access the outer function's variables, creating a scope chain. This should make sense now that we understand scope and hoisting. The following example should be helpful:

let learn = 'learning experience! ';
let result;
function phrase(a){
  return function(b) {
    return (a + b + learn)

result = phrase('Enjoy ')('your ');
console.log(result); //=> Enjoy your learning experience! 

The innermost return of this function uses variables that have been declared outside of the inner function. This is possible because the scope chain is maintained by the inner function. When the function is executed, this scope chain allows access to the variables that live inside the outer function. This example would also work with var (but don't!) and will not work with const due to the need to initialize variables using const at the time of declaration.

Hopefully this helps to clarify some of the more mysterious parts of JavaScript functionality. Understanding these tricky parts will help you write better code!

Top comments (0)