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