8 Important Javascript Concepts Every Web Developer Should Know

8 Important Javascript Concepts Every Web Developer Should Know

Intro

Javascript is one of the most popular programming languages in the world. It is widely used as a client-side programming language by almost 98% of all the websites. So, if you clicked this article, you already know what javascript is. Whether you are a beginner at JavaScript or have already worked on some small projects. You must understand these 8 core JavaScript concepts. Let’s dive into it.

Lexical Scope

In Javascript, the access to a variable depends on its scope. In other words, variable access depends on where in the code you declare it and which keyword you use to declare it. For Example, if you declare a variable in the outermost part of your code, before defining any function or starting any other block of code, that variable is globally scoped and can be accessed anywhere in the code. If you want to access a globally scoped variable inside a function, you can do it. If you want to access a globally scoped variable inside an inner function that is inside an outer function, you can do it. On the other hand, as you can probably guess, a variable declared inside a function is only scoped for that function and cannot be accessed outside of that function. Take a look at the code below:

var globalVar = "I am a globally scoped variable";

const outer = () => {

  var outerVar = "I am a local variable declared within the outer function";

  console.log(globalVar); //You can access globalVar inside outer function as it is a global variable
  console.log(outerVar); //You can also access outerVar here as it is defined in its own scope

  const inner = () => {
    console.log(globalVar,outerVar); //You can access both globalVar and outerVar inside the inner function

    var innerVar = "I am variable declared within the inner function";
  }

  console.log(innerVar); //Error: you cannot access innerVar outside of the inner function
}

outer();
console.log(outerVar); //Error: You cannot access localVar outside the function it was declared in

The scope of a variable also depends on the keyword you use to declare it. A variable declared with var is within the function it is declared in. Whereas the scope of a variable declared with let or a constant declared with const is only within the block it is declared. A block of code is any code that is inside a parentheses {}. Therefore, a variable declared using let keyword inside an if block cannot be accessed outside of that block.

Note: It is not a good practice to declare too many variables globally. Global variables remain in the memory throughout the execution of the code, which is not good for performance. Therefore, it is a good practice to declare variables only within the function or block where they are needed. Additionally, in other languages (Python, C, Java, etc) variables are scoped within a block, hence the use of let over var is always recommended.

Closures

To understand closures it is important to understand lexical scoping, so make sure you have properly read the previous section. In the previous section, we learned that the inner function can access variables declared in its own scope, the outer function’s scope, and the global scope. And the outer function has access to the variable declared in its own scope and the global scope. We also learned that once the function is done executing, it removes the variable declared inside it from the memory. A closure is a function that has access to its outer function scope even after the outer function is done executing and has returned.

By definition, a closure is the combination of a function and the lexical environment within which that function was declared. If this sounds complicated, don’t worry, lets take a look at an example below:

const init = () => {
    var a = 4;
    const add = () => {
        var b = 2;
        return a + b;
    }

    return add;
}

let addition = init();
console.log(addition());

//Run ▶️
//6

In the example above we can see that the init function returns a reference to the add function and the reference is saved in the addition variable. Notice that after returning the reference to the add function, the init function is done executing but the add function inside it still has access to the variable a. That is because javascript creates a lexical scope for the inner function add and keeps all the variables it is accessing in memory even if that variable was declared in the outer function init. In this example. the function add is a closure.

Hoisting

Before we understand Hoisting, let’s take a look at the example code below:

myName("Ahmed");

const myName = (name) => {
    console.log("My name is " + name);
}

Do you think the code above will throw an error because we are using the function myName before we are declaring it? The answer is no it will not throw an error.

//Run ▶️
//My name is Ahmed

This is called Hoisting. Javascript interpreter move the declaration of variables, classes, and functions to the top of their scope. Hoisting allows the safe execution of functions before they are declared.

Hoisting also works with variables. However, it is important to keep in mind that javascript only hoists declaration, not initialization. Let’s take a look below:

console.log(name); // Returns 'undefined' from hoisted var declaration
var name; // Declaration
name = "Ahmed"; // Initialization
console.log(name); // Result: "Ahmed". Because we are using it after the initialization

Callback Functions

Another important javascript concept to understand is the callback function. It is simply a function that is passed to another function as a parameter and is then invoked inside that function.

Here is a quick example:

const displayName = (firstName, secondName) => {
    console.log(firstName + secondName);
}

const userInput = (callBack) => {
    var firstName = prompt('Enter firstName:');
    var secondName = prompt('Enter secondName:');
    callBack(firstName, secondName);
}

userInput(displayName);

Note: In the example above, only the reference to the displayName function is passed to userInput. If you execute it while passing, it will throw an error.

userInput(displayName()); //don't do this

Note: In ES5, callbacks were the main way of handling asynchronous operations.

See also: Asynchronous Programming

Promises

Promises are the foundation of asynchronous programming in modern JavaScript. Promises in javascript are just like promises in real life. You promised to do something, when you do it, you have fulfilled the promise (resolved), and if you don’t do it, you have failed your promise (rejected) and you give a reason (error Message). Similarly, in javascript when a promise is called, it will start in a pending state. The calling function continues executing, while the promise is pending until it is resolved or rejected. If the promise is resolved, it is passed to the callback function (then) and if rejected, it is passed to the callback function (catch). Let’s take a look at the example below.

let workDone = true;

const checkWorkDone = new Promise((resolve, reject) => {
    if (workDone) {
        const successMessage = "The work is successfully done";
        resolve(successMessage);
    } else {
        const errMessage = "There is some error. Work is not done";
        reject(errMessage);
    }
});

checkWorkDone.then(message => {
    console.log(message);
}).catch(error => {
    console.log(error);
});

//Run▶️ 
//The work is successfully done

Async Await

Previously we have learned two different javascript methods to handle asynchronous operations:

  • ES5 → Callback function
  • ES6 → Promises

In ES7, Async Await was introduced. Async Await is promises behind the scene, so you must read the previous section. Async Await is actually syntactic sugar on top of promises to make asynchronous functions look synchronous. And the error handling is done using try catch. Let’s look at the code below:

let workDone = true;

const doSomethingAsync = () => {
  return new Promise((resolve, reject) => {
    if (workDone) {
      const successMessage = "The work is successfully done";
      resolve(successMessage);
    } else {
      const errMessage = "There is some error. Work is not done";
      reject(errMessage);
    }
  });
};

const doSomething = async () => {
  try {
    const message = await doSomethingAsync();
    console.log(message);
  } catch (err) {
    console.log(err);
  }
};

doSomething();

//Run▶️
//The work is successfully done

Async Await is widely used to perform fetch API requests where you want the data to fully load before pushing it into view.

const getUserData = async () => {
  const response = await fetch('/users.json');
  const users = await response.json(); // parse JSON
  const user = users[0]; // pick first user
  const userResponse = await fetch(`/users/${user.name}`);
  const userData = await userResponse.json(); // parse JSON
  return userData;
};

getFirstUserData();

Equality

This is a very simple, yet important concept to know about javascript. In javascript, there is a double equal (==) and a triple equal (===) syntax to compare values. Both syntaxes work differently. With double equal syntax, javascript will convert the data types of two values to be exactly the same before doing an equality check. For example:

let a = 26;
let b = "26";

console.log(a == b); 

//Run▶️
//true

With triple equal syntax, javascript won’t do any data type conversion and instead will compare both values as they are.

let a = 26;
let b = "26";

console.log(a === b); 

//Run▶️
//false

It is also important to understand that the equality check works differently with objects as compared to primitive values. When comparing two objects, javascript won’t check if both objects have the same values inside them. Instead, it will check if both object variables are pointing to the same object in memory.

let a = {type: "number", value: 26};
let b = {type: "number", value: 26};

console.log(a === b); 

//Run▶️
//false

let x = {type: "number", value: 26};
let y = x;

console.log(x === y); 

//Run▶️
//true