A short journey to Javascript

Anmol Agarwal
13 min readJan 30, 2021

I would like to start from the beginning where we need to know the basic understanding of what javascript is and how it works.

Please bear with me for some time ☕️

What is Javascript ?

Javascript is multi-paradigm, JIT compiled programming language (JIT is implemented by modern browsers eg. V8 engine, Chakra), single-threaded (execute one piece of code at a time in call stack) supporting object-oriented, imperative and declarative styles.

Scope of the post

Keywords that will cover the core concepts of Javascript -

  1. Execution Context and Lexical Environment
  2. Hoisting
  3. Scope and Closures
  4. Object
  5. Prototype Inheritance
  6. ES6 features

Before diving into execution, let’s first understand the terminology.

What is Execution Context ?

Execution Context is a space where our code will be executed. Execution Context is created when the control is transferred to the executable code(the code to be executed) which is not associated with any execution context. Execution Call Stack works on LIFO (Last In First Out) data structure.

Javascript starts executing the code by creating an execution context called Global Execution Context and every time a function is called, a new execution context is created and pushed into the call stack. Just Remember , every execution context has 2 phases: Creation Phase and Execution Phase.

What is Lexical Environment ?

Lexical Environment is a specification type used to specify the association of identifiers(names given to variables, functions etc) based upon the lexical nesting structure of ECMAScript code. A lexical Environment consist of Environment Record and a reference (possibly null) to the outer lexical environment.

Reference from — ( Link)

A lexical environment is created when certain syntax structure of code (for example — FunctionDeclaration, Block Statement , catch block of TryCatch) executes. An outer lexical environment may be referred as an outer environment by multiple inner lexical environments such as a function containing 2 inner functions.

1)    var number = 20;2)    function divisible(number) {
3) var d = 10;
4) if (number % d == 0)
5) return true;
6) }
7) divisible(number);

Now let’s see the execution now. Remember I mentioned that javascript first compile and then interpreted. During the compilation phase, in the Variable Environment, all variables are created with undefined value and function declaration is assigned into the memory.

Hence, Global Execution Context(GEC) is created containing global object (global object is created only for GEC), this keyword (referring to global object), number variable with value as undefined and divisible function created with function body. After compilation, the engine immediately executes the code from start again.

During the execution phase —

  1. On line 1 number is assigned the value as 20
  2. On line 7, divisible function is called.

Now as soon as a function divisible is invoked with () sign, a new execution context is created which is termed as function execution context. This function execution context is pushed into the context stack/call stack(whatever you want to call it).

Now that function execution context contains this object, arguments (array of arguments passed to the function), number parameter which is already assigned the value as 10 during the creation phase, d variable is created with value as undefined.

During the FEC execution phase —

  1. On line 3, d is assigned the value as 10
  2. Now the if condition executes and return boolean value.

After the function is executed, the function execution context is popped out of the stack and counter returns to the Global Execution Context. and we do not have any more operation left after line 7, the GEC is popped out and program ends.

Now we have seen the basic execution of javascript code, one thing you might have noticed in above code is why variable gets undefined value during creation phase whereas function body gets stored into the memory. The reason is because of hoisting.

Hoisting

Hoisting is a conceptual mechanism to explain this behaviour where variables and functions are put into the memory during compilation phase and in less formal terms to say, they are moved to the top of code.

Let’s see with one example to get a clear picture

console.log(foo); //undefinedvar foo = 1;
bar();
function bar() {
if (!bob) {
var bob = 10;
}
console.log(bob); //prints 10
}

In the above example, we are printing foo before creating it and calling bar before defining it. What will be printed ?

Because of hoisting, all the new variables and function declaration of the running execution context is moved to the top (ie foo and bar). Now when the execution phase starts when the code is actually running, line 1 will print undefined since foo is already created during compilation phase and assigned undefined value. On line 3 we are calling bar function and since function declaration is already hoisted at the top, the bar function will get executed and new function execution context is created.

The FEC is pushed onto the stack, now in the compilation phase engine looks for any new variable or function to hoist. It will see a new variable bob defined with var keyword on line 6 and hoist it. Now during execution phase when line 5 is executed bob is undefined and will enter into the condition and assign bob as 10. On line 8, bob will print 10

Note — Only variable and function declarations are hoisted and not any variable initializations . Function expressions are also not hoisted hence we can’t use before defining them.

Now what about this ? Wait I am talking about this binding . But for this to understand we need to understand Scope first. As per from our execution contexts, we have only global and function execution context. Hence every new execution context creates a new scope i.e either global or local/function scope.

Scope

Scope refers to the execution context where the variables, objects and function can be found.It defines the accessibility of those identifiers. If they are not present in that scope, it will throw reference error.

It’s important to know where to look for the values and how to access them. Scope is generally categorized into following types —

  1. Global Scope
  2. Local Scope or Function Scope
  3. Block Scope (works with let, const)

There is only one global scope throughout the Javascript program when you actually start writing the code. Variables inside the global scope can be accessed inside function scope.

var foo = 1;
var bob;
function bar() {
var foo = 2;
function baz(foo) {
var noddy = "cartoon";
foo = 3;
bob = 4;
}
baz();
}
bar();
console.log(bob);

In above example,

  1. a foo variable is initialized in the global scope.
  2. Function bar is called on line 12 we see that foo variable on line 4 is created but since function has it’s own scope, foo will be created as a local variable.
  3. Function baz is executed on line 5 and we see an implicit declaration of variable foo that we get as a named parameter.
  4. On the next line, foo is initialized as 3, but remember we are referencing the local variable that we just declared in the last step within the scope of function baz.
  5. On the next line, we see a variable bob, Since baz doesn’t have any reference for variable bob, hence it will look outside of it’s scope i.e inside function bar. Primarily a function first look on it’s own scope and if it finds the reference then okay otherwise it will look for outer lexical environment scope and so on (also called scope chaining). Now bar does not have any reference to variable bob, so it again goes to the outer lexical environment (which is global scope in the case) and see a declaration for bob on line 2. It takes the reference from the global scope (since bob is defined inside the global scope) and assign 4 to it. One thing I would like to point out for variable bob. Though we are accessing it inside the function baz, it is getting leaked to the global scope which we should avoid. In strict mode, it doesn’t allow to create any non-reference variable in the global scope, hence it will give you error in strict mode.
  6. Now we are done executing the baz(), hence it’s execution context will be popped out from the stack. Since there is nothing left to execute inside bar(), it’s execution context will be also popped out and now we are in the global execution context.
  7. Last line print’s the value of variable bob. Now you can predict the value it will be going to print. Take a pause and think. The answer is 4 since you had a reference for a global variable hence any change you make to the global variable will be reflected.

You also noticed that we have also defined variable noddy. If you try to print the variable noddy outside the function baz(), it will give you reference error. Because it is lexically scoped inside the function baz. By lexical I mean that variables defined outside the function can be accessed inside the function but when we try to access the variable from outside the function, we can’t access it (like for noddy above).

Let’s talk about some block scope. ES6 introduced two new keywords — let and const . Variables declared with let/const inside a block {} cannot be accessed from outside the block. Block scope can be applied on loops, conditions, function etc.

function sum() {
if (true) {
let a = 1;
console.log(a); //prints 1
}
console.log(a); //ReferenceError
}

Here variable a defined with let keyword within if block creates a scope inside that block and can’t be accessed outside the if block. Variables created with let/const can be hoisted but unlike var they are not initialized with undefined value. Variables declared with let and const are only initialized when their assignment (also known as lexical binding) is evaluated during runtime by the JavaScript engine.

console.log(item);   //ReferenceError
let item = "global";
console.log(item2); //undefined
var item2 = "global2"

Once variables are defined with const, you cannot change it’s value. It’s a good practice to define the variables at the top of the scope.

Closures

Closure is when function remembers it’s lexical scope even when it is executed outside that lexical scope.

Definition taken from Kyle Simpson (You don’t know JS) textbook.

function foo () {
let bar = 1;

return function () {
console.log(bar);
}
}
const baz = foo();
baz(); // prints 1

While execution, foo() returns a function which gets stored in baz on line 7. Since foo is finished with the execution , variable bar ceased to exist since it’s a local variable.

We see that we came back to the global execution context. Now when we try to execute function baz() on last line, we notice that the returned function does not have lexical scope anymore but still it is printing the correct value? The reason is because during the execution, inner function creates a closure around itself of the outer lexical scope . Hence the inner function will create a closure and store bar property.

Let’s look at other example

function click() {
var isLogin = true;

$("btn").click(function () {
console.log(isLogin);
});
}
click();

Suppose click() function gets executed during page rendering. But inner function will only be rendered during button click. We know that variables created in function are destroyed when function finished execution but also function creates closures to remember the lexical scope at a later point hence isLogin will be accessed after the click.

I hope by now you understand the basic concepts, stop reading here and try with few examples in your browser console.

Objects

Objects in Javascript can be compared to an object in real life which has some characteristics associated with it. For eg — Bottle is an object which has color, height as it’s properties attached to it. A property of an object is defined as variable and can be accessed with dot notation.

const bottle = new Object();bottle.color = "blue";
bottle.brand = "cello";

Properties of Javascript can also be accessed with bracket notation —

const bottle = new Object();bottle["color"] = "blue";
bottle["brand"] = "cello";

Properties which are not valid Javascript identifiers (having hyphen, space or starts with a number) can only be accessed with the bracket notation like obj[“x-token”] = token

Assignment vs Deep vs Shallow copy

A new variable is created when you try to copy a primitive value with the help of assignment operator so if you make any change in the new variable it won’t affect the original variable. But the same are not for objects, if you try to copy the value of reference variable into new variable it will copy the reference to the source obj. So if you try to change one of it’s properties then you will change the value of the original object.

let star = "Sun";
let futureNeutronStar = star; //Changing the star value won't
change the futureNeutronStar value
//Object Reference
let galaxy = {
name: "Milky Way",
planets: 9,
centerStar: "Sun"
};
let newGalaxy = galaxy;
newGalaxy.planets = 8; //Updates the source Object
galaxy.planets; //8

There was no predefined function in Javascript which creates a copy of object until ES6 introduced Object.assign() and spread operator which do a shallow copy of object. However for a deep copy, you may need to use some alternatives in javascript like JSON.parse(JSON.stringify(obj)) or some third party libraries (eg- lodash)

Object.assign() is used to copy the values of all the enumerable (the properties which show up when you loop through that object) own properties from one or more source object to target source.

const source1 = { key1: "value1", key2: "value2" };
const source2 = { key3: "value3", key4: "value4" };
const targetObj = Object.assign({}, source1, source2);//Lets see shallow copy
const obj = {
key1: "v1",
key2: {
key3: "v2",
key4: "v3"
}
}
let newObj = Object.assign({}, obj);
newObj.key2.key3 = "v4";
console.log(obj); //Original Source obj's key3 value is changed// Since key3 and key4 are not enumerable property of obj, a reference is saved for key2 properties in newObj

So in order to clone the complete object, you need to do a deep copy rather than shallow copy.

Let’ s see different ways of creating objects in javascript

  1. Using Initializers to create a new object
var obj = {
key1: "val1",
2: "val2",
"key 3": function () {
console.log(this.key1);
}
}
obj.key1 // val1
obj[2] // val2
obj["key 3"]() // val1

Along with variables, function can also be defined as properties of object aka methods.

2. Using Constructor functions

var obj = new Object();

We can create new objects by using new keyword to call the function as constructor function. Here Object is a constructor function which is used and initialize the obj with whatever it returns.

You can create your own constructor functions or functions which can behave as a constructor.

function Book () {
this.title = "The Kite Runner",
this.author = "Khaled Hosseini"
}
const book = new Book();

3. Object.create

The create function creates a new object with a specified prototype (the object you want to inherit the properties from) that you are allowed to choose without any need to define the constructor function.

const obj = Object.create({});  //creates empty objectconst mobile = {
company: "dummy",
boot: function () {
console.log(`${this.company} Make sound`);
}
}
let nokia = Object.create(mobile);
nokia.company = "Nokia";
nokia.boot();

Prototype Inheritance

Since Javascript is object oriented programming Language but unlike other OOPS patterns, the object can’t only inherit properties from Class but can also inherit from other object’s as well through object’s prototypal inheritance.

Object instances are created via constructor functions, Object literals and Object.create(). In the above example you created a new variable nokia which has access to the mobile properties.

JavaScript objects are dynamic “bags” of properties (referred to as own properties). JavaScript objects have a link to a prototype object. When trying to access a property of an object, the property will not only be sought on the object but on the prototype of the object, the prototype of the prototype, and so on until either a property with a matching name is found or the end of the prototype chain is reached.

let obj = function () {
this.foo = "foo",
this.bar = "bar"
};
let obj1 = new obj(); // {foo: "foo", bar: "bar"}console.log(obj1.foo) // foo
// is foo an own property of obj1. Yes, hence console it

obj.prototype.baz = "baz";

console.log(obj1.baz);
// is baz an own property of obj1. No, so look at prototype of obj1
// is baz an own property of obj1[[Prototype]]. Yes, hence console

When you execute the above code in browser console, you will see something like above. Here foo and bar are own properties of obj1 where as baz is the property of obj1 prototype and is inside the __proto__ property. Since baz was not a physical property of obj1 so it will check in the __proto__ of obj1. Both prototype and __proto__ are same but the terms are used relatively based on whether it’s a function or an object instance. So it will keep on looking into the prototype chain until it will find that property.

Apart from prototype chain, Javascript has concatenative inheritance by which you can directly inherit features from objects to objects using Object.assign (ES6) or Lodash’s extend.

We discussed about creating objects and how we can inherit properties of functions and objects using Constructor functions, ES6 Object.create() and Object.assign(). You task is to try with multiple examples and check the prototype relationships.

ES6 Features

Arrow Functions

const hello = () => {
console.log(`Hello ${this.name}`);
}

We create arrow functions by adding () followed by => and after this we write function body inside {} . For single line statement you can skip {} and write your statement

const hello = () => console.log(`Hello ${this.name}`);

Object Destructing

The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.

const country = {
name: "India",
region: "Asia",
states: [
{ name: "UP", language: "Hindi" },
{ name: "KA", language: "Kannada"}
];
}
//Destructing Object
//just mention what properties you want
const { name, region, states, cuisine = "Veg" } = country;
console.log(name); // India
console.log(region); // Asia
console.log(states); // Prints the state array [{}, {}]
//You can assign default values to properties
console.log(cuisine); // Veg

Spread Operator

Spread Operator allow an iterable entity to expand its values such as Arrays, Objects

const number = [1, 2, 3, 4];console.log(...number);
const obj = {
key1: "v1",
key2: "v2"
};
const newObj = Object.assign({}, { "key": "val" , ...obj});
console.log(newObj); // {key: "val", key1: "v1", key2: "v2"}

I hope by now you get an understanding of what Javascript offers.

Thanks for Reading.

--

--