How the JavaScript engine works with operators

In this post we will go over the following concepts in JavaScript
– Operators
– Operator Precedence
– Operator Associativity
– Coercion

Operator Basics

We are taught from a young age that a plus sign between two numbers results in the addition of them and one number returned. To keep this same syntax in code rather than something like add(x,y), we have operators in JavaScript. Operators are special functions that are invoked differently syntactically than regular functions.

result = 2 + 2 - 5;

We have some operators in the above code block, the assignment(=) operator, the addition(+) operator and the subtraction(-) operator.
– The assignment operator will assign a value to it’s left operand based on what is it’s right operand.
– Ignoring primitive type coercion now which we’ll get to later in this post, the addition operator will return the sum of the left and right numeric operand.
– The subtraction operator will return the difference between the left and the right numerical operand.

Now that we know what these operators do I’d like to walk you through how the JavaScript engine will interpret the code block. However, we are still unable to fully grasp what is going on with just this knowledge. We’re missing two concepts that are essential to understanding what is going on, operator precedence and operator associativity.

Operator Precedence is a value that is assigned to each operator which helps it determine which operator gets to execute first. Operator Associativity is what order the operator gets called in when they have the same precedence. Addition and Subtraction both have a precedence of 14 and are left to right associative. Assignment has a precedence of 3 and is right to left associative.

The JavaScript engine will interpret (result = 2 + 2 – 5) as follows:
The engine compares the operator associativity of the operators and finds that addition and subtraction are higher so they will be executed first. Since addition and subtraction are left to right associative, addition will be executed first with the values 2 and 2 leaving us with the following expression
result = 4 – 5
Then the engine will invoke the subtraction operator
result = -1
Finally the engine will assign the value of -1 to the spot in memory that result is pointing to and return -1 to from the assignment which is thrown away as nothing will be using this value after this is over.

var = var2 = 4

Assignment returns the value of what was assigned. This allows us to write some concise code like above. Here the second assignment is invoked first which return a value of 4 after assigning it to var2. Then the first written assignment operator is invoked and var is assigned the value of 4.

Comparison Operators and Variable Coercion

if (0 == null) {
    console.log("JavaScript thinks null is the same as zero.")
} 

if (0 === null) {
    console.log("JavaScript thinks null is the same as zero.")
}

How many times will the phrase, “JavaScript thinks null is the same as zero.”, be logged to the console? If you’ve been working with JavaScript in the past, you may know that the phrase will only be logged once. The two if statements are identical except they use different operators. The equality operator(==) and the strict equality operator(===) differ in one key way. The equality operator will attempt to coerce both variables to be the same type before checking for equality contrary to the strict operator. The equality operator will coerce the value null to 0 and the expression will return true. The strict operator will not do so and return false.

JavaScript having these properties can lead to very powerful intuitive code when used properly and can also result in painful debug sessions when not used without thought. Here is one example where coercion can be very helpful.

// unknownParameter is the result of an api call, it will return a string if successful or an empty string if not successful
let unknownParameter = api("get")

let parameter = unknownParameter || "default"

JavaScript’s coercion property allows us to write some powerful, concise and readable code. The logical or(||) operator has a higher precedence than the assignment operator and will execute before it. If the api call failed and the unknownParameter’s value is an empty string, JavaScript will coerce the empty string to false and move on to the right operand “default”. Any string other than an empty string coerces down to true. JavaScript engine will then take this value that coerced to true and return the uncoerced variable which is assigned to parameter. If the api call had succeeded, unknownParameter would have coerced to true and been returned from the logical or operator. This is some heavy lifting that was done in only a few characters, powerful stuff!

I hope you have a better understanding of what’s happening behind the scenes when it comes to JavaScript’s use of operators. If I’ve made any errors or you have any questions please feel free to post any comments!

Bonus: Here is a helpful link for operator precedence if you’d like to explore!
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

Understanding base JavaScript concepts Part I

We will be inspecting the following concepts and explain how the JavaScript engine is working in the browser:
– Execution Context
– Scope Chain and the Lexical Environment
The following index.html will be accompanying all app.js files used to demonstrate the above concepts.

//Index.html

<html>
    <head>
        
    </head>
    <body>

        <script src="app.js"></script>
    </body>
</html>

Execution Contexts

//App.js

When I load the index.html file into the browser with the empty app.js file, I would expect to see nothing happening in the console or web page. We haven’t told JavaScript to do anything but does that mean that JavaScript has done nothing since there are no lines of code?

There is in fact one object available to us in the console, the window object!

We also have this same window object assigned to this

Both of these objects were given to us because JavaScript has built a Global Execution Context for us.

The Global Execution Context Visualized

In JavaScript, an Execution Context is the environment that JavaScript code runs in. The Global Execution Context is the base Execution Context. The Global Execution Context creates two things for you, a ‘Global Object’ and ‘this‘. As we saw earlier, in the browser window object is the Global Object and it is assigned to this at the global level. Other things in the Execution Context are the Outer Environment and Your Code. In a Global Execution Context the Outer Environment is null. In this case, since we have nothing in our app.js file, we have nothing in the Execution Context for Your Code.

Further Breaking Down the Execution Context

Covered concepts:
– Hoisting
– Execution Context Creation
– Execution Context Stack
– Function Invocation
– Single Threaded
– Synchronous

//app.js
console.log(var1);
fun1();

var var1 = "var1";

function fun1() {
	console.log("fun1")
}
fun1();
console.log(var1);

What would you expect the console output of the following code to be? When I first looked at this code, I thought that either the JavaScript engine would throw an error or the variable and function instantiation would not matter and it would print this sequence, “var1” “fun1” “fun1” “var1”. Instead, this was the console output!

We’ll investigate why this output was like this

The first console log of var1 was undefined yet the first invocation of fun1 printed “fun1”. This is because of a concept called hoisting. The Execution Context has two phases, inside of the first phase, Creation Phase, the JavaScript engine sets up memory space for functions and variables The difference here between the variable and function is that the engine will put the entire function into the value of the memory key whereas the variable is initialized with undefined, a special keyword in JavaScript.

The second phase, the Execution phase is where code is being run line by line inside of the Execution Context environment. Anytime we have function invocation, a new Execution Context is created and put onto the Execution stack. This is visualized below using the following app.js file.

//app.js

function fun1() {
    // Do nothing
}

function fun2() {
    fun1()
}

fun2()

Here when the program is starting, a Global Execution Context is created. fun1 and fun2 are assigned places in memory, the global object is created and this is assigned to it during the Creation Phase.


During the execution phase, the JavaScript engine makes it’s way to the invocation of fun2 and immediately creates an Execution Context for fun2 adding on top of the Execution Stack. Inside of the Execution Phase of fun2’s Execution Context, fun1 is invoked creating another execution context to add to the Execution Stack. Once all code in fun1 is resolved we will pop off the fun1 execution context, then fun2 execution context once it’s resolved and then the Global Execution Context. Now the JavaScript Engine has nothing more to do.

JavaScript is single-threaded and synchronous meaning the above code was executed one line at a time in a single execution context at a time.

Scope Chain and the Lexical Environment

//app.js

function fun1() {
	console.log(myVar, "fun1")
}

function fun2() {
	var myVar = 4;
	console.log(myVar, "fun2");
	fun1()
}

var myVar = 2;
console.log(myVar, "Global Scope");
fun2();

Above, we have three console log statements. Our output is shown below:

Does this output surprise you? Where JavaScript code physically exists is important. This is what the Lexical Environment is, where your code is physically defined. The Lexical Environment determines what the outer environment is for the Execution Context. This is then a crucial piece in the Scope Chain. Here, fun1 was defined in the Global environment. The fun1 Execution Context has to look to its outer environment to find the myVar variable. Because it was defined in the Global Environment, it will look in the Global Environment next. It doesn’t matter that fun1 was invoked inside of fun2, just where it was defined. If fun1 was defined and invoked inside of fun2, it would have console logged a value of 4 and “fun1.”

The Scope Chain will not stop linking to the next Execution Context down until the outer environment is null which is the Global Execution Context.

Wrapping Up

I hope you have a better understanding of what’s happening, under the hood, in JavaScript. Please leave comments if I’ve gotten anything wrong or you have any inquiries!

Design a site like this with WordPress.com
Get started