DEV Community

Vinay
Vinay

Posted on • Edited on

Some of the JS.

In this blog I'll be sharing some of the JS concepts that I've learnt and used till now.

  1. Single threaded JS.
  2. JS engine and the browser.
  3. ECMAScript.
  4. A little bit about Date.
  5. How JS executes.
  6. Closures.
  7. Async-await.
  8. Some quotes from Eloquent JS.

JS is single threaded.

JS is single threaded. It only has one call stack. It does not have multiple threads. It uses the environment that it is running in to deal with the asynchronous tasks. For web apps, it uses the browser APIs to execute the sync/async tasks. Here is the list of browser APIs, in case you want to know about them.

JS engine and browser.

Most popular JS engine is Chrome V8 and along with Chrome browser, it's also used by NodeJS. A JS engine provides everything that is specified in ECMAScript standard. Out of all these, memory heap and call stack will be the focus of this blog. Memory heap is used for the space allocation and call stack is used for executing the code. We'll go in detail about the execution steps later. So in summary, the JS engine provides the runtime environment for JS to execute. The engine is independent of the browser but it collaborates with it.

ECMAScript

Initially, I was a bit confused about the differences and would use this name interchangeably with javascript. These are the two different entities. JavaScript is the language that follows a standard called ECMAScript. This is the standard that any language has to follow to be called an ECMASCript language. JScript is another language that follows this specification. The standards are specified in the document called ECMA 262. These standards are decided by a group called TC39. This group has programmers, academics and many others who have a major contribution in deciding the course of this specification called ECMAScript. You can read more about this here

A little bit about Date

JS has provided the date object and its methods which can be used to manipulate dates in our app. Out of those, there is a method called getDate() which returns the date. There have been times when I was required the number of days of a month. It can be done by using

new Date(2022,month,0).getDate()
Enter fullscreen mode Exit fullscreen mode

0 here tells JS to give number of days of month previous to what we have specified. month here is a digit and months are 0 indexed in JS. So if I want to check number of days in Feb 2022, i call it like

new Date(2022,2,0).getDate()
Enter fullscreen mode Exit fullscreen mode

Here 2 works because I want month previous to March. So when you read it, it aligns with our notion of addressing months as 1-indexed. Hence, its easier to remember.

How JS executes.

Now, how does JS execute in the browser. JS is an interpreted language. This does not mean it does not get compiled. It gets compiled during the runtime unlike languages like C++ that are compiled well before execution. This Just in Time compilation is done by the JS engine. Apart from this, browser provides APIs like DOM, AJAX etc. You might not know this but console is also an API and it is not part of JS. In case of console object, it's easier to understand the program flow because it is not an async API. But what happens when an async task is performed. Let's say we have an async task of fetching some data and we are using fetch API.

fetch("https://rickandmortyapi.com/api/character?page=1", {
  method: "GET"
})
  .then((res) => res.json()) //callback function to convert response stream to JSON 
  .then((data) => console.log(data.results));
Enter fullscreen mode Exit fullscreen mode

Browser fetches the data from the url specified and the callback function that was specified is then pushed to the microtask queue to be executed by the call stack. Event loop is what takes care of providing these tasks back to the call stack from the queue. As soon as the call stack is empty or it only has global context (we'll talk about execution context later), event loop pops that callback function from the queue and pushes it in the stack. Now, there is one more queue called event queue which has a lower priority than microtask queue. Any task that uses a promise based API like fetch, is pushed in the microtask queue and any other async task like setTimeout is pushed to event queue. In a case where both the queues have a task waiting to be pushed in the stack, priority is given to microtask queue. After this, call stack executes the callback function and the execution continues.

Now, to go in the details, the JS executes in two phases. First is the memory allocation phase and the second is code execution phase. Before going in details of these phases, I would like to tell you about execution context, scope and lexical environment. We talked about call stack before, that executes the code. But what exactly does the element of that stack represent? It's not lines of code, it is execution context. At the bottom of the call stack or the first element inside the call stack is the global execution context. This is because every program starts from the global scope. Every time a function is created, a separate execution context is created for that function and this context is pushed on top of the stack. This execution context is popped from the stack as soon as the code within that function is done executing.

Lexical environment takes care of the mapping of variables with their references. This is called variable-identifier mapping. Identifier is the name of the variable and variable is the actual reference. So, every execution context has it's lexical environment. When the execution context is popped from the call stack, the lexical environment of that context is not disposed. It stays in the memory for any future use. For example, lets take a scenario of enclosing functions.

function outerFunc() {
  let a = "a";
  function innerFunc() {
    let b = 'b'
    console.log(a,b);
  }
  innerFunc();
}
outerFunc();
Enter fullscreen mode Exit fullscreen mode

Here, the outer and inner functions will have their own respective contexts and lexical environments. The lexical environment of the inner function will not only keep account of variables in it's scope but also keep a reference to the parent environment which in this case is the outer function. That is how we can use variable that belongs to the outer function. This part might have reminded you about the closures.

Scope literally means the 'extent'. Thinking from the perspective of programming, it can be the extent to which a particular statement is impacting/affecting the program or the extent to which a particular variable can be utilized.

Closures

Now about the closures. If you remember, lexical environment remembers it's parent lexical environment. This way you can imagine a chain of these environments that is formed where every enclosed environment holds a reference to it's respective parent environment until we reach the global lexical environment. This chain is what enables the enclosed function to use bindings from the enclosing function, way after that enclosing function was invoked. This is called closure. Here is an example,

function outerFunc() {
  let a = "a";
  return function() {
    let b = "b";
    console.log(a, b);
  };
}
outerFunc()();
Enter fullscreen mode Exit fullscreen mode

Now that these terminologies are clear. Let's resume with phases of execution. In the first phase, lexical environments are initialized. This is the stage where variables and functions are hoisted. In this second phase, the thread of execution starts.

Async-await

When I started using async await syntax, I was under the impression that the program literally halted at the await statement and resumed the execution when the promise was settled. I realized that this is not how it works. Here is an example,

function first() {
  return new Promise((res) => {
    console.log(2);
    res(3); /* promise resolved */
    console.log(4);
  });
}

async function f() {
  console.log(1);
  let p = await first();
  console.log(5);
  console.log(p); /* resolved promise */
}

console.log("a");
f();
console.log("b");
Enter fullscreen mode Exit fullscreen mode

Guess the output for this program. If you thought the output would be a 1 2 4 5 3 b, then you need to know this one thing about async-await. The program doesn't halt and wait for the promise to be settled. Instead, await statement and anything that is after the await statement in that particular scope, is moved from the call stack to the microtask queue to be pushed back again to the call stack, once the promise has settled. In this case, console.log(5) and console.log(p) is moved and program resumes execution from console.log('b') in the global scope. Once this is completed, the call stack gets emptied and those statements are pushed to the stack to be executed. So, the order of the execution will be a 1 2 4 b 5 3.

Some quotes from Eloquent JS

  • "Being able to refer to an instance of a local binding from an enclosing scope is closure."
  • "Function remembers the environment it is created in, not the one it is invoked in."
  • "A function parameter is a local binding by itself."
  • "Pure function not only avoids causing side effects but also avoids relying on side effects caused by other functions."

This was all I wanted to share for now, hope it helps. Thanks for reading.

Top comments (0)