How variables really work?

Photo by Nik on Unsplash

How variables really work?

The conversations between the engine, compiler and scope.

We all use variables every day, it's one of the most fundamental parts of any programming language, without it we'd only be able to create very simple programs. However, do we fully understand how they're understood and used by the engine, particularly the JS engine in our case?

Let's find out!

Compiler

We know that the JS code is also compiled but not like other languages, it's compiled JIT (Just-in-time) for execution, which can happen just milliseconds (or less) after compilation, it goes through a process called Tokenisation/Lexing to tokenise our code statements, such as var x = 2; will be broken into a stream of tokens that would then be parsed into an AST (Abstract Syntax Tree) and finally Code generation happens which then sends it to the machine in a way that the machine understands. We won't cover the process in any more detail for this blog. Let's take a look at what is relevant from this process for this blog.

When the compiler sees the code var x=2, its job during the tokenisation process is to ensure there is a space allocated in the memory for that variable, at this stage the value is not important. If the variable is already present in the scope, it moves on, otherwise, it asks the scope to declare a variable in that scope collection.

The compiler then produces the code for the assignment x=2 to be executed by the engine later.

What happens during execution?

The engine now executes the code produced by the compiler, it looks up if the variable has already been declared, and it does so in the scope collection. But how? Simple no? See if an 'x' already exists, yes? But there are two different types of look-ups that the engine would do.

LHS!=RHS

There are two types of lookups, LHS (Left-hand side) and RHS (you can probably guess this one)

  1. When an assignment happens the engine needs to do an LHS, that is, it needs to find the container for that variable if it exists (created by the scope during lexing) and then assign the given value to it.

  2. When a value is referenced like in the case below, we do an RHS lookup for the variable 'a' when being used to assign the value to 'b', RHS essentially means, go and fetch the value for the variable.

     var a=2;
     var b=a+2;
    

These terms don't necessarily mean Left or Right side of the expression, as there are other ways assignment to a variable happens, for example

function foo(a){
console.log(a);
}

foo(2);

Here, also, the engine has to do 3 RHS look-ups, that is first it needs to look up whether a function foo exists, then for the console object, it will need to check all the scopes and finally, the 'a' variable that we're passing to the log method, it will have to do an RHS too. Can you spot the LHS lookup(s) though?

Let me help you! the value that we pass to the function gets implicitly assigned to the first param of the function, for it, it also needs to do an LHS lookup and then assign. Now that you know the first one, can you tell the second one?
The value that we passed to the log method also gets assigned to the first param of it implicitly.

Scope difference

How does it matter which look-up is happening, isn't it all the same? Apparently, not. We know that the scope of a variable is resolved lexically, that is, if the variable referenced is not present in the current scope, it will be searched for in the outer scope till the final scope is reached (Global/ Window in browsers).

Let's see where the difference lies...

When an RHS lookup is unable to find the variable value, it goes to the outer scope, till the global scope is reached, regardless of whether the value is found or not, the search will be stopped, and in case the value is not found a ReferenceError is thrown by the engine, meaning that we have not found the variable anywhere, you must declare it.

console.log(undeclaredVariable); //Throws Reference error

Let's look at how LHS does it differently, look at the code below:

function foo(){
  a=10;
}
foo();
console.log(a); // what will be the output

Anyone who's looked up interview questions might be able to easily identify this one, the answer is that 'a' is defined in the global scope but the explanation might be a little tricky to provide if you've not understood the difference in lookup behaviour.

When an LHS lookup isn't able to find the variable in any of the scopes till the global scope, our friend, the scope intervenes and says "You didn't declare the variable but I just took care of it for you" and defines it in the last searched scope which will always be the global scope, and therefore this variable is declared in the global scope.

Strict Mode

As you can tell, LHS behaviour is a little unexpected and might cause unintentional memory leaks, therefore when the strict mode is enabled, the behaviour is disabled

'use strict'
function foo(){
a=10;
}
foo();
console.log(a); // ReferenceError

I'd be deep diving into JavaScript and React in the coming weeks and will blog most of it here and on twitter (now X), consider following if you want to learn how JS and React really work internally, thanks!