20 Essential JavaScript Concepts Every Developer Should Know for Success

JavaScript Concepts Every Developers Should Know

This article will dive into 20 essential JavaScript concepts every developer should know, with clear explanations, syntax, and examples to eliminate any confusion. JavaScript is the language of the web.

Whether you’re a front-end developer or working full-stack, understanding the core javascript concepts is vital. JavaScript has evolved from a simple scripting language to a powerhouse capable of building complex web applications, mobile apps, and even backend services.

List of JavaScript Concepts

Let’s dive into 20 essential JavaScript concepts every developer should know, with clear explanations, syntax, and examples to eliminate any confusion.

1. Variables (var, let, const)

Variables are containers for storing data values and one of the important javascript concepts. JavaScript offers three keywords for declaring variables, each with different scoping rules.

Prior to ES6 (ECMAScript 2015), var was the only way to declare variables. However, var has some quirks, especially regarding scoping (explained in detail in javascript concepts 9). let and const were introduced to provide more precise control over variable scope and mutability, making code more predictable and less prone to errors.

  • var:

    • Function-scoped: This means a var variable declared inside a function is only accessible within that function. If declared outside any function, it’s globally scoped.
    • Hoisted: Its declaration is “lifted” to the top of its scope during the compilation phase, but its initialization (assignment) stays in place. This can lead to undefined values if you try to access it before its assignment.
    • Can be re-declared and re-assigned: You can declare a var variable with the same name multiple times in the same scope without an error, which can lead to accidental overwriting of values.
  • let:

    • Block-scoped: This means a let variable is only accessible within the block ({}) where it’s defined (e.g., inside an if statement, for loop, or function). This prevents issues like variable leakage.
    • Hoisted (but in Temporal Dead Zone): let variables are also hoisted, but they are not initialized. If you try to access a let variable before its declaration, you’ll get a ReferenceError. This period is known as the Temporal Dead Zone (TDZ).
    • Can be re-assigned but not re-declared: You cannot declare a let variable with the same name twice in the same scope, which helps prevent naming conflicts and accidental re-declarations.
  • const:

    • Block-scoped: Similar to let, const variables are block-scoped.
    • Hoisted (but in Temporal Dead Zone): Also subject to the Temporal Dead Zone.
    • Cannot be re-assigned or re-declared: Once a const variable is initialized, its value cannot be changed, and it cannot be re-declared. This ensures that the variable always points to the same value or reference.
    • Important Note for Objects/Arrays: While the variable itself cannot be re-assigned, if const holds an object or an array, the contents of that object or array can be modified. const only ensures that the variable always points to the same memory address.

Syntax:

var name = "John";
let age = 30;
const PI = 3.14;

Example:

function declareVariables() {
var car = "Toyota"; // Function-scoped
if (true) {
let bike = "Honda"; // Block-scoped
const color = "Red"; // Block-scoped
console.log(bike); // Honda
console.log(color); // Red
}
// console.log(bike); // ReferenceError: bike is not defined
// console.log(color); // ReferenceError: color is not defined
console.log(car); // Toyota
}
declareVariables();

Explanation of Example:

  • var car = “Toyota”;: car is declared with var inside declareVariables. It’s accessible throughout this function.
  • let bike = “Honda”; and const color = “Red”;: These are declared inside the if block. They are only accessible within this block.
  • bike = “Suzuki”;: This line demonstrates that let variables can be re-assigned.
  • // color = “Blue”;: If uncommented, this would throw an error because const variables cannot be re-assigned.
  • The console.log(bike) and console.log(color) lines outside the if block are commented out because they would result in ReferenceError. This clearly shows the block-scoping of let and const.
  • console.log(car) outside the if block works because car is var-declared and thus function-scoped, making it accessible throughout the declareVariables function.

2. Data Types

JavaScript is a dynamically typed language, meaning you don’t explicitly declare the data type of a variable.

Understanding data types is crucial because they affect how values are stored, how operations are performed on them, and how they behave in comparisons. It is one of the important JavaScript concepts. JavaScript divides data types into two main categories:

  1. Primitive Data Types:

    • string: Represents textual data. Enclosed in single (”), double (“”), or backticks (`). Backticks allow template literals for embedded expressions and multi-line strings.
    • number: Represents both integer and floating-point numbers.
    • boolean: Represents a logical entity and can have two values: true or false.
    • null: Represents the intentional absence of any object value. It’s a primitive value.
    • undefined: Indicates that a variable has been declared but has not yet been assigned a value, or that a property does not exist.
    • symbol (ES6): Represents a unique identifier. Used for creating unique property keys that won’t clash with other properties.
    • bigint (ES2020): Represents whole numbers larger than 2^53 – 1 (the maximum for number). Appended with n.
  2. Non-Primitive (Reference) Data Type:

    • Object: Used to store collections of data and more complex entities. Arrays and functions are special types of objects. Unlike primitive values, objects are stored by reference, meaning variables hold a reference to the memory location where the object is stored, not the object itself.

Syntax: (Implicitly determined by the value)

Example:

let str = "Hello, JavaScript!"; // string (textual data)
let age = 30; // number (integer)
let price = 99.99; // number (floating-point)
let isActive = true; // boolean (logical true)
let isAvailable = false; // boolean (logical false)
let emptyValue = null; // null (intentional absence of value)
let notAssigned; // undefined (variable declared but no value assigned)
let uniqueId = Symbol('user_id'); // symbol (unique identifier)
let reallyBigNumber = 9007199254740991n + 100n; // bigint (for very large integers)

let person = { // object (key-value pairs)
name: "Alice",
age: 25,
city: "New York"
};

let colors = ["red", "green", "blue"]; // object (array is a type of object)

let greetFunction = function() { // object (function is a type of object)
console.log("Greetings!");
};

console.log("Type of str:", typeof str); // Output: Type of str: string
console.log("Type of age:", typeof age); // Output: Type of age: number
console.log("Type of isActive:", typeof isActive); // Output: Type of isActive: boolean
console.log("Type of emptyValue:", typeof emptyValue); // Output: Type of emptyValue: object (a well-known JavaScript quirk)
console.log("Type of notAssigned:", typeof notAssigned); // Output: Type of notAssigned: undefined
console.log("Type of uniqueId:", typeof uniqueId); // Output: Type of uniqueId: symbol
console.log("Type of reallyBigNumber:", typeof reallyBigNumber); // Output: Type of reallyBigNumber: bigint
console.log("Type of person:", typeof person); // Output: Type of person: object
console.log("Type of colors:", typeof colors); // Output: Type of colors: object
console.log("Type of greetFunction:", typeof greetFunction); // Output: Type of greetFunction: function (but functions are also callable objects)

Explanation of Example:

  • Each let declaration assigns a value of a specific data type.
  • typeof operator is used to check the data type of a variable.
  • str is a string because it holds text.
  • age and price are numbers, illustrating both integer and float capabilities.
  • isActive and isAvailable are booleans, representing true/false states.
  • emptyValue is null. The typeof null returning object is a historical bug in JavaScript that persists for backward compatibility.
  • notAssigned is undefined as it’s declared but has no value.
  • uniqueId is a symbol, guaranteeing uniqueness.
  • reallyBigNumber is a bigint, demonstrated by the n suffix, allowing for numbers beyond the standard number type’s safe integer limit.
  • person is a plain object with key-value pairs.
  • colors is an array, which typeof reports as object because arrays are a specialized form of objects in JavaScript.
  • greetFunction is a function, which typeof also correctly identifies as function (even though functions are callable objects).

3. Operators

Operators perform operations on values and variables.

Understanding operators is crucial for writing any meaningful logic. JavaScript has a rich set of operators, categorized by the type of operation they perform:

  • Arithmetic Operators: Used for mathematical calculations.
    • + (addition), – (subtraction), * (multiplication), / (division), % (remainder/modulo), ** (exponentiation, ES2016), ++ (increment), — (decrement).
  • Assignment Operators: Used to assign values to variables.
    • = (assignment), += (add and assign), -= (subtract and assign), *= (multiply and assign), /= (divide and assign), %= (modulo and assign), **= (exponentiate and assign).
  • Comparison Operators: Used to compare two values and return a boolean (true or false).
    • == (loose equality): Compares values after type coercion (tries to convert types to match). Often leads to unexpected results and should be avoided in most cases.
    • === (strict equality): Compares values and types without type coercion. Preferred for reliable comparisons.
    • != (loose inequality): Opposite of ==.
    • !== (strict inequality): Opposite of ===. Preferred for reliable comparisons.
    • > (greater than), < (less than), >= (greater than or equal to), <= (less than or equal to).
  • Logical Operators: Used to combine or negate boolean expressions.
    • && (Logical AND): Returns true if both operands are true. Short-circuits (evaluates left operand first, if false, returns it and doesn’t evaluate right operand).
    • || (Logical OR): Returns true if at least one operand is true. Short-circuits (evaluates left operand first, if true, returns it and doesn’t evaluate right operand).
    • ! (Logical NOT): Inverts the boolean value of its operand.
  • Ternary (Conditional) Operator: A shorthand for an if-else statement.
    • condition ? exprIfTrue : exprIfFalse
  • Bitwise Operators: Perform operations on the binary representation of numbers. (Less common in typical web development, but useful for specific low-level tasks).
    • & (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (signed right shift), >>> (unsigned right shift).
  • String Operators:
    • + (concatenation): Used to join strings.
    • += (concatenation assignment).
  • Type Operators:
    • typeof: Returns a string indicating the data type of its operand.
    • instanceof: Checks if an object is an instance of a particular class or constructor function.

Example:

let num1 = 20;
let num2 = 10;
let text1 = "Hello";
let text2 = "World";
let isSunny = true;
let isWeekend = false;

// Arithmetic Operations
console.log("Addition:", num1 + num2); // Output: Addition: 30
console.log("Subtraction:", num1 - num2); // Output: Subtraction: 10
console.log("Multiplication:", num1 * num2); // Output: Multiplication: 200
console.log("Division:", num1 / num2); // Output: Division: 2
console.log("Remainder:", num1 % 3); // Output: Remainder: 2 (20 divided by 3 is 6 with a remainder of 2)
console.log("Exponentiation:", num2 ** 2); // Output: Exponentiation: 100 (10 raised to the power of 2)

// Assignment Operators
let x = 5;
x += 3; // Equivalent to x = x + 3;
console.log("x after +=:", x); // Output: x after +=: 8

// Comparison Operators
console.log("Loose Equality (20 == '20'):", 20 == '20'); // Output: Loose Equality (20 == '20'): true (type coercion)
console.log("Strict Equality (20 === '20'):", 20 === '20'); // Output: Strict Equality (20 === '20'): false (different types)
console.log("Strict Inequality (10 !== 5):", 10 !== 5); // Output: Strict Inequality (10 !== 5): true
console.log("Greater than (num1 > num2):", num1 > num2); // Output: Greater than (num1 > num2): true

// Logical Operators
console.log("AND (isSunny && isWeekend):", isSunny && isWeekend); // Output: AND (isSunny && isWeekend): false (true && false is false)
console.log("OR (isSunny || isWeekend):", isSunny || isWeekend); // Output: OR (isSunny || isWeekend): true (true || false is true)
console.log("NOT (!isSunny):", !isSunny); // Output: NOT (!isSunny): false

// Ternary Operator
let message = (num1 > 15) ? "Num1 is greater than 15" : "Num1 is not greater than 15";
console.log("Ternary Result:", message); // Output: Ternary Result: Num1 is greater than 15

// String Concatenation
let fullName = text1 + " " + text2;
console.log("Full Name:", fullName); // Output: Full Name: Hello World

Explanation of Example:

  • Arithmetic: Standard mathematical operations are shown. num1 % 3 gives the remainder of 20 divided by 3. num2 ** 2 calculates $10^2$.
  • Assignment: x += 3 is a shorthand for x = x + 3, demonstrating how assignment operators update a variable’s value.
  • Comparison:
    • 20 == ’20’ returns true because == performs type coercion, converting the string ’20’ to the number 20 before comparison.
    • 20 === ’20’ returns false because === checks both value and type. A number is not strictly equal to a string. This highlights why === is generally preferred.
    • 10 !== 5 returns true because 10 is indeed not strictly equal to 5.
  • Logical:
    • isSunny && isWeekend is true && false, which evaluates to false.
    • isSunny || isWeekend is true || false, which evaluates to true.
    • !isSunny negates true to false.
  • Ternary: A concise way to assign a value based on a condition. (num1 > 15) is true, so message becomes “Num1 is greater than 15”.
  • String Concatenation: The + operator joins text1, a space, and text2 to form fullName.

4. Conditional Statements (if, else if, else, switch)

Control the flow of execution based on certain conditions. conditional statements are one of important javascript concepts

Choosing between if-else if-else and switch depends on the complexity and type of condition.

  • if-else if-else:

    • Used when you need to evaluate a series of different conditions.
    • Conditions can be complex boolean expressions.
    • The first condition that evaluates to true will have its corresponding block of code executed, and the rest of the else if and else blocks will be skipped.
    • The else block is optional and executes if none of the preceding if or else if conditions are met.
  • switch:

    • Used when you have a single expression that you want to compare against multiple possible fixed values.
    • It’s often more readable and efficient than a long chain of else if statements when checking for exact matches.
    • The break keyword is crucial within switch statements. Without break, execution will “fall through” to the next case even if the current case matched, which is usually not desired (though sometimes intentionally used for specific logic).
    • The default keyword is optional and specifies the code to run if none of the case values match the expression.

Syntax:

// if-else if-else
if (condition1) {
// code to execute if condition1 is true
} else if (condition2) {
// code to execute if condition2 is true
} else {
// code to execute if no condition is true
}

// switch
switch (expression) {
case value1:
// code to execute if expression matches value1
break;
case value2:
// code to execute if expression matches value2
break;
default:
// code to execute if no match
}

Example

let score = 85;

if (score >= 90) {
console.log("Grade A");
} else if (score >= 80) {
console.log("Grade B");
} else {
console.log("Grade C");
}

let day = "Monday";
switch (day) {
case "Monday":
console.log("Start of the week.");
break;
case "Friday":
console.log("End of the week.");
break;
default:
console.log("Mid-week.");
}

5. Loops (for, while, do-while, for…of, for…in)

Execute a block of code repeatedly.

Choosing the right loop depends on your specific iteration needs and Loops are one of important Javascript concepts:

  • for loop:
    • Best when you know the number of iterations beforehand or when you need explicit control over the iteration process (e.g., iterating with an index, incrementing by a specific step).
    • It has three optional parts: initialization (executed once before the loop starts), condition (checked before each iteration), and increment/decrement (executed after each iteration).
  • while loop:
    • Best when the number of iterations is unknown and depends on a condition being met.
    • The condition is checked before each iteration. If the condition is initially false, the loop body will never execute.
    • Requires careful management of the condition variable to avoid infinite loops.
  • do-while loop:
    • Similar to while, but the loop body is executed at least once before the condition is checked.
    • Useful when you need to perform an action at least once, regardless of the initial condition.
  • for…of loop (ES6+):
    • Designed for iterating over iterable objects (e.g., Arrays, Strings, Maps, Sets, NodeLists).
    • It directly gives you the values of the elements.
    • More concise and readable for iterating over elements than a traditional for loop for arrays.
  • for…in loop:
    • Designed for iterating over the enumerable properties (keys) of an object.
    • It iterates over the keys (property names) of an object.
    • Caution: It also iterates over inherited enumerable properties and can sometimes include unexpected properties if not careful. It’s generally not recommended for iterating over arrays because it iterates over indices (which are property names) and can include non-numeric properties or iterate in an unexpected order. For arrays, for…of or forEach are much better.

Syntax:

// for loop
for (initialization; condition; increment/decrement) {
// code to execute
}

// while loop
while (condition) {
// code to execute
}

// do-while loop
do {
// code to execute
} while (condition);

// for...of
for (const item of iterable) {
// code to execute
}

// for...in
for (const key in object) {
// code to execute
}

Example:

for (let i = 0; i < 3; i++) {
console.log("For loop:", i); // 0, 1, 2
}

let count = 0;
while (count < 2) {
console.log("While loop:", count); // 0, 1
count++;
}

const colors = ["red", "green", "blue"];
for (const color of colors) {
console.log("Color:", color); // red, green, blue
}

const person = { name: "Jane", age: 25 };
for (const key in person) {
console.log(`${key}: ${person[key]}`); // name: Jane, age: 25
}

6. Functions

Blocks of reusable code that perform a specific task. Functions can take parameters and return a value.

Functions are “first-class citizens” in JavaScript, meaning they can be treated like any other value: assigned to variables, passed as arguments to other functions, and returned from other functions.

There are several ways to define functions:

  1. Function Declaration:

    • The traditional way to define a named function.
    • Hoisted: Can be called before their definition in the code.
    • function functionName(parameters) { /* … */ }
  2. Function Expression:

    • A function defined as part of an expression (e.g., assigned to a variable).
    • Not hoisted in the same way as declarations: The variable holding the function is hoisted, but its value (the function itself) is not assigned until the line of assignment is executed. You cannot call it before its definition.
    • const functionName = function(parameters) { /* … */ };
  3. Arrow Functions (ES6+):

    • A more concise syntax for writing function expressions.
    • Do not have their own this context (they lexically inherit this from the enclosing scope).
    • Cannot be used as constructors (i.e., with new).
    • Do not have the arguments object.
    • const functionName = (parameters) => { /* … */ };
    • Shorthand for single-expression return: const add = (a, b) => a + b; (no return keyword needed, no curly braces).

Key Concepts within Functions:

  • Parameters: Variables listed inside the parentheses in the function definition. They are placeholders for values that will be passed into the function.
  • Arguments: The actual values passed to the function when it is called.
  • return statement: Specifies the value that the function sends back to the caller. If no return statement is specified, the function implicitly returns undefined.

Syntax:

// Function Declaration
function functionName(param1, param2) {
// code to execute
return result;
}

// Function Expression
const functionName = function(param1, param2) {
// code to execute
return result;
};

// Arrow Function (ES6+)
const functionName = (param1, param2) => {
// code to execute
return result;
};
// Shorthand for single expression
const add = (a, b) => a + b;

Example:

function greet(name) {
return "Hello, " + name + "!";
}
console.log(greet("Alice")); // Hello, Alice!

const multiply = (a, b) => a * b;
console.log(multiply(4, 5)); // 20

7. Arrays

Ordered collections of data. Elements can be of any data type.

Arrays are one of the most frequently used data structures in JavaScript and are one of important javascript concepts. They come with a rich set of built-in methods for manipulation.

Key Characteristics:

  • Ordered: Elements maintain their insertion order.
  • Indexed: Elements are accessed by their numerical index (0-based).
  • Heterogeneous: Can store elements of different data types (though often best practice to store elements of the same type for consistency).
  • Dynamic Size: You can add or remove elements, and the array’s size adjusts automatically.

Common Array Methods (a small selection):

  • Adding/Removing Elements:
    • push(): Adds one or more elements to the end of an array.
    • pop(): Removes the last element from an array and returns it.
    • unshift(): Adds one or more elements to the beginning of an array.
    • shift(): Removes the first element from an array and returns it.
    • splice(start, deleteCount, …items): Changes the contents of an array by removing or replacing existing elements and/or adding new elements.
  • Accessing/Iterating:
    • arr[index]: Access an element by its index.
    • length: Property that returns the number of elements in an array.
    • forEach(callback): Executes a provided function once for each array element.
    • map(callback): Creates a new array populated with the results of calling a provided function on every element. (Higher-order function, see concept 14)
    • filter(callback): Creates a new array with all elements that pass the test implemented by the provided function. (Higher-order function)
    • reduce(callback, initialValue): Executes a reducer function on each element of the array, resulting in a single output value. (Higher-order function)
  • Searching:
    • indexOf(element): Returns the first index at which a given element can be found in the array, or -1 if it is not present.
    • includes(element): Determines whether an array includes a certain value among its entries, returning true or false.
  • Transforming:
    • join(separator): Joins all elements of an array into a string.
    • concat(arr2, …): Used to merge two or more arrays.

Syntax:

const arrayName = [element1, element2, ...];

Example:

const fruits = ["apple", "banana", "orange"];
console.log(fruits[0]); // apple
fruits.push("grape"); // Add element
console.log(fruits); // ["apple", "banana", "orange", "grape"]
fruits.pop(); // Remove last element
console.log(fruits.length); // 3

8. Objects

Unordered collections of key-value pairs. Keys are strings (or Symbols), and values can be any data type.

Objects are JavaScript’s core building blocks for almost everything. They are unordered collections of properties.

Key Characteristics:

  • Unordered: Property order is not guaranteed (though modern engines often preserve insertion order for string keys).
  • Key-Value Pairs: Data is stored as key: value. Keys are strings (or Symbols). Values can be anything.
  • Dynamic: Properties can be added, modified, or deleted after an object is created.

Creating Objects:

  • Object Literal (most common): const obj = { key: value, … };
  • new Object(): const obj = new Object(); obj.key = value; (less common for simple objects).
  • Constructor Functions / Classes (ES6+): Used for creating multiple objects of the same “type” with shared methods. (See concept 16).
  • Object.create(): Creates a new object with the specified prototype object and properties. (See concept 15).

Accessing Properties:

  • Dot Notation: object.propertyName (preferred when property name is a valid identifier).
  • Bracket Notation: object[‘propertyName’] (useful when property names contain spaces, special characters, or are dynamic/variables).

Common Object Operations:

  • Adding/Updating Properties: object.newProperty = value; or object[‘newProperty’] = value;
  • Deleting Properties: delete object.property;
  • Iterating over Properties: for…in loop (see concept 5), Object.keys(), Object.values(), Object.entries().
  • Method Definition: Functions defined as properties of an object are called methods.

Syntax:

const objectName = {
key1: value1,
key2: value2,
// ...
};

Example:

const user = {
firstName: "John",
lastName: "Doe",
age: 30,
isLoggedIn: true
};

console.log(user.firstName); // John
console.log(user["age"]); // 30

user.email = "john.doe@example.com"; // Add new property
console.log(user);
// { firstName: 'John', lastName: 'Doe', age: 30, isLoggedIn: true, email: 'john.doe@example.com' }

9. Scope (Global, Function, Block)

Determines the accessibility of variables and functions.

Understanding scope is critical for avoiding naming conflicts, writing modular code, and preventing unintended side effects.

  1. Global Scope:

    • Variables and functions declared outside of any function or block have global scope.
    • They are accessible from anywhere in your JavaScript code (including within functions and blocks).
    • Caution: Relying heavily on global variables can lead to “global namespace pollution,” where different parts of your code accidentally use the same variable names, causing conflicts and making debugging difficult.
  2. Function Scope:

    • Variables declared with var inside a function are function-scoped.
    • They are only accessible within the function where they are declared and its nested functions.
    • They are not accessible from outside the function.
  3. Block Scope (ES6+):

    • Variables declared with let and const are block-scoped.
    • A “block” is a pair of curly braces {} (e.g., inside if statements, for loops, while loops, or simply a standalone block).
    • Variables declared with let or const inside a block are only accessible within that block. This provides more granular control over variable visibility and prevents variable leakage from blocks.

Example:

let globalVar = "I am global";

function myFunc() {
var functionVar = "I am function-scoped";
if (true) {
let blockVar = "I am block-scoped";
console.log(blockVar); // Accessible
}
// console.log(blockVar); // ReferenceError
console.log(functionVar); // Accessible
}
myFunc();
console.log(globalVar); // Accessible
// console.log(functionVar); // ReferenceError

10. Hoisting

Hoisting is a JavaScript mechanism where variable and function declarations are moved to the top of their containing scope (global, function, or block) during the compilation phase, before the code is executed. This means you can use variables and functions before you declare them in your code.

Hoisting can be a source of confusion, especially when mixing var with let/const.

  • var variables:

    • Their declarations are hoisted to the top of their function or global scope.
    • They are initialized with undefined during the hoisting phase.
    • This means you can access a var variable before its explicit declaration, but its value will be undefined until the actual assignment line is reached.
  • let and const variables:

    • Their declarations are also hoisted to the top of their block scope.
    • However, they are not initialized.
    • Accessing a let or const variable before its declaration results in a ReferenceError because they are in a “Temporal Dead Zone” (TDZ) from the beginning of the block until their declaration. This is a deliberate design choice to make JavaScript more predictable and prevent common errors associated with var.
  • Function Declarations:

    • Function declarations (e.g., function myFunction() {}) are fully hoisted. This means both the function’s name and its definition are moved to the top of the scope.
    • You can call a function declaration before it appears in the code.
  • Function Expressions/Arrow Functions:

    • These are treated like variable declarations. If declared with var, the variable is hoisted and initialized to undefined. If declared with let or const, they are subject to the TDZ.
    • You cannot call a function expression or arrow function before the line where it’s assigned.

Example:

console.log(hoistedVar); // undefined (var is hoisted and initialized)
var hoistedVar = "Hello";

// console.log(hoistedLet); // ReferenceError: Cannot access 'hoistedLet' before initialization
// let hoistedLet = "World";

hoistedFunction(); // "I am a hoisted function"
function hoistedFunction() {
console.log("I am a hoisted function");
}

11. this Keyword

Refers to the context in which a function is executed. Its value depends on how the function is called.

  • Global context: this refers to the global object (window in browsers, global in Node.js).
  • Method call: this refers to the object that owns the method.
  • Function call (strict mode): this is undefined.
  • Function call (non-strict mode): this refers to the global object.
  • Arrow functions: this is lexically scoped (inherits this from the parent scope).
  • Event handlers: this refers to the element that triggered the event.

Example:

const person = {
name: "Peter",
greet: function() {
console.log("Hello, " + this.name); // 'this' refers to 'person'
}
};
person.greet(); // Hello, Peter

const anotherGreet = person.greet;
anotherGreet(); // Hello, undefined (in non-strict mode, or ReferenceError in strict mode if 'this' is undefined)

const arrowFunc = {
name: "Mary",
sayHello: () => {
console.log("Hello, " + this.name); // 'this' refers to the global object (or outer lexical 'this')
}
};
arrowFunc.sayHello(); // Hello, undefined (or the global 'name' if it exists)

Explanation for the arrow function example: In a browser environment, if you run arrowFunc.sayHello();, this.name inside the arrow function will refer to window.name, which is typically an empty string or undefined. This is because arrow functions do not have their own this context; they inherit this from their enclosing lexical context. In the global scope, this is window.

12. Asynchronous JavaScript (Callbacks, Promises, Async/Await)

Deals with operations that don’t execute immediately (e.g., network requests, timers) to prevent blocking the main thread.

Asynchronous JavaScript deals with operations that don’t complete immediately. Instead of waiting for these operations to finish (which would “block” the main thread and freeze the user interface), JavaScript uses asynchronous patterns to allow the program to continue executing other tasks while waiting for the long-running operation to complete. Once the asynchronous task finishes, a specified callback function or subsequent promise chain is executed.

  1. Callbacks:

    • The oldest and most basic way to handle asynchronous code.
    • A callback is a function passed as an argument to another function, which is then executed when the asynchronous operation completes.
    • Problem: Can lead to “Callback Hell” or “Pyramid of Doom” when multiple nested asynchronous operations are involved, making code hard to read and maintain.
  2. Promises (ES6+):

    • An object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
    • A promise can be in one of three states:
      • pending: Initial state, neither fulfilled nor rejected.
      • fulfilled (or resolved): Meaning the operation completed successfully.
      • rejected: Meaning the operation failed.
    • Provides a cleaner way to handle sequential asynchronous operations (.then()) and error handling (.catch()).
    • Methods:
      • .then(onFulfilled, onRejected): Attaches callbacks for the resolution or rejection of the Promise.
      • .catch(onRejected): Attaches a callback for only the rejection case.
      • .finally(onFinally): Attaches a callback that will be executed regardless of whether the Promise is fulfilled or rejected.
  3. Async/Await (ES2017):

    • Syntactic sugar built on top of Promises, making asynchronous code look and behave more like synchronous code, greatly improving readability.
    • async keyword: Used to declare an asynchronous function. An async function always returns a Promise. If it returns a non-Promise value, JavaScript automatically wraps it in a resolved Promise.
    • await keyword: Can only be used inside an async function. It pauses the execution of the async function until the Promise it’s waiting for resolves (or rejects). If the Promise resolves, await returns its resolved value. If it rejects, await throws an error, which can be caught using try…catch.

Syntax:

// Callback
function doSomething(callback) {
setTimeout(() => {
callback("Data fetched!");
}, 1000);
}

// Promise
const fetchData = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched!");
// reject("Error fetching data!");
}, 1000);
});

// Async/Await
async function getData() {
try {
const data = await fetchData;
console.log(data);
} catch (error) {
console.error(error);
}
}

Example:

doSomething((message) => {
console.log("Callback:", message);
});

fetchData.then((data) => {
console.log("Promise:", data);
}).catch((error) => {
console.error("Promise Error:", error);
});

getData();

13. Closures

A closure is the combination of a function and the lexical environment within which that function was declared. It allows a function to “remember” its outer variables even after the outer function has finished executing.

Closures are a powerful feature in JavaScript that are often used implicitly. They enable data privacy, function factories, and maintaining state in asynchronous operations.

How it works:

  1. When an inner function is defined inside an outer function.
  2. The inner function forms a “closure” over the local variables of its outer function.
  3. Even when the outer function completes execution and its execution context is popped off the call stack, the inner function retains a reference to the outerVar (or any other variables from its outer scope).
  4. When the inner function is later executed, it can still access and manipulate those “remembered” variables.

Example:

function outerFunction(outerVar) {
return function innerFunction(innerVar) {
console.log("Outer variable:", outerVar);
console.log("Inner variable:", innerVar);
};
}

const newFunction = outerFunction("I am outer");
newFunction("I am inner");
// Outer variable: I am outer
// Inner variable: I am inner

14. Higher-Order Functions

Functions that either take one or more functions as arguments or return a function as their result. Common examples include map, filter, reduce.

HOFs are powerful because they abstract over actions, not just values. Instead of specifying what to do with data directly, you specify how to do it by passing a function.

Common HOFs in JavaScript (especially for Arrays):

  • Array.prototype.forEach(): Executes a provided function once for each array element. (Doesn’t return a new array).
  • Array.prototype.map(): Creates a new array populated with the results of calling a provided function on every element in the calling array.
  • Array.prototype.filter(): Creates a new array with all elements that pass the test implemented by the provided function.
  • Array.prototype.reduce(): Executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
  • Array.prototype.sort(): Sorts the elements of an array in place and returns the sorted array. Can take a comparison function as an argument.
  • Array.prototype.some(): Tests whether at least one element in the array passes the test implemented by the provided function.
  • Array.prototype.every(): Tests whether all elements in the array pass the test implemented by the provided function.

Example:

const numbers = [1, 2, 3, 4, 5];

// map: Transforms each element
const doubledNumbers = numbers.map(num => num * 2);
console.log(doubledNumbers); // [2, 4, 6, 8, 10]

// filter: Creates a new array with elements that pass a test
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]

// reduce: Reduces an array to a single value
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 15

15. Prototypes and Prototypal Inheritance

JavaScript uses prototypal inheritance, where objects inherit properties and methods from other objects via a prototype chain. Every JavaScript object has a [[Prototype]] (accessible via __proto__ or Object.getPrototypeOf()).

This concept is foundational to how objects and “classes” work in JavaScript.

  • The Prototype Chain: When you try to access a property x on an object obj:
    1. JavaScript first checks if x is directly on obj.
    2. If not, it checks obj’s prototype (obj.__proto__).
    3. If still not found, it checks obj.__proto__.__proto__, and so on.
    4. This continues until null is reached at the end of the chain. If x is not found anywhere, it’s considered undefined.
  • Inheritance: Objects “inherit” properties and methods by being linked to a prototype object. All built-in objects (like Arrays, Functions, Dates) also follow this model, inheriting from Object.prototype (which sits at the top of most chains, and its prototype is null).
  • Object.prototype: The base object that all other objects eventually inherit from. It contains common methods like toString(), hasOwnProperty(), etc.
  • Constructor Functions: Historically, objects with shared behavior were created using constructor functions. The prototype property of a constructor function is what gets linked as the __proto__ of new instances created with new.
  • new keyword: When new MyConstructor() is called:
    1. A brand new empty object is created.
    2. The [[Prototype]] of this new object is set to MyConstructor.prototype.
    3. The MyConstructor function is executed with this bound to the new object.
    4. If the constructor function does not explicitly return an object, this (the new object) is returned.

Syntax: (Implicit in object creation, or explicitly using Object.create())

Example:

const animal = {
sound: "makes a sound",
makeSound: function() {
console.log(this.sound);
}
};

const dog = Object.create(animal);
dog.sound = "Woof!";
dog.makeSound(); // Woof!

console.log(dog.__proto__ === animal); // true

 

16. Classes (ES6+)

Syntactic sugar over JavaScript’s existing prototypal inheritance model. They provide a more familiar object-oriented syntax.

While classes look like traditional classes, it’s crucial to remember that they are still fundamentally built on prototypes.

Key Features:

  • class keyword: Used to declare a class.
  • constructor method: A special method for creating and initializing an object created with a class. It’s called automatically when new is used.
  • Methods: Functions defined inside the class are automatically added to the class’s prototype, meaning they are shared by all instances.
  • extends keyword: Used to create a subclass (child class) that inherits from a parent class.
  • super keyword: Used within a subclass constructor to call the parent class’s constructor, or within a subclass method to call a parent class’s method. It must be called in the subclass constructor before using this.
  • static methods: Methods that belong to the class itself, not to instances of the class. They are called directly on the class (e.g., MyClass.staticMethod()).
  • Getter/Setter methods: Special methods to define computed properties, allowing you to control how properties are accessed and modified.

Syntax:

class ClassName {
constructor(param1, param2) {
this.param1 = param1;
this.param2 = param2;
}

method1() {
// ...
}

static staticMethod() {
// ...
}
}

Example:

class Vehicle {
constructor(make, model) {
this.make = make;
this.model = model;
}

start() {
console.log(`${this.make} ${this.model} started.`);
}
}

class Car extends Vehicle { // Inheritance
constructor(make, model, color) {
super(make, model); // Call parent constructor
this.color = color;
}

drive() {
console.log(`Driving the ${this.color} ${this.make} ${this.model}.`);
}
}

const myCar = new Car("Toyota", "Camry", "Blue");
myCar.start(); // Toyota Camry started.
myCar.drive(); // Driving the Blue Toyota Camry.

 

17. Modules (ES6+)

Allow you to break down your code into smaller, reusable pieces (modules) and then import them where needed. This helps with organization, reusability, and avoiding global namespace pollution.

Before ES Modules, developers relied on patterns like IIFEs (Immediately Invoked Function Expressions) or CommonJS (for Node.js) to achieve modularity. ES Modules are now the official and preferred way for both browser and Node.js environments.

Key Concepts:

  • export: Used to make variables, functions, classes, or objects available for use in other modules.
    • Named Exports: export const name = …; or export function func() { … }; You can export multiple named exports from a single file.
    • Default Export: export default myVariable; or export default class MyClass { … }; Each module can have at most one default export. It’s typically the main entity the module provides.
  • import: Used to bring exported members from other modules into the current module.
    • Named Imports: import { name, func } from ‘./module.js’; The names must match the exported names.
    • Default Import: import MyClass from ‘./module.js’; The name you give the default import can be anything.
    • Importing all named exports: import * as ModuleAlias from ‘./module.js’; (Then access as ModuleAlias.name, ModuleAlias.func).
  • Module Scope: Each module has its own top-level scope. This means variables declared in a module are private to that module by default, unlike global script scope.
  • Strict Mode by Default: Modules are always executed in strict mode, even without “use strict”;.
  • Single Instance: A module is executed only once. If imported multiple times, it returns the same instance, preserving state.

Syntax:

// myModule.js
export const greeting = "Hello from module!";
export function sayHi() {
console.log("Hi!");
}
export default class MyClass { /* ... */ }

// app.js
import { greeting, sayHi } from './myModule.js';
import MyClass from './myModule.js'; // For default export

console.log(greeting);
sayHi();
const obj = new MyClass();

Example: (Requires a module-supporting environment, e.g., a browser with type="module" or Node.js)

// utils.js
export const PI = 3.14159;
export function calculateArea(radius) {
return PI * radius * radius;
}

// app.js
import { PI, calculateArea } from './utils.js';

console.log(PI); // 3.14159
console.log(calculateArea(5)); // 78.53975

 

18. Error Handling (try…catch…finally)

Mechanisms to deal with runtime errors gracefully, preventing your application from crashing.

Errors are an inevitable part of programming. Effective error handling ensures that your application remains robust and provides a better user experience.

  • try block:

    • Contains the code that might throw an error.na
    • If an error occurs within this block, the execution of the try block stops, and control is immediately transferred to the catch block.
    • If no error occurs, the catch block is skipped.
  • catch block:

    • Contains code that is executed if an error occurs in the try block.
    • It takes one argument, which is the Error object that was thrown. This object contains information about the error (e.g., name, message, stack).
    • Used for logging the error, displaying user-friendly messages, or attempting to recover.
  • finally block:

    • Contains code that is always executed, regardless of whether an error occurred in the try block or was caught by the catch block.
    • It’s optional but very useful for cleanup operations (e.g., closing files, releasing resources, completing network requests).
  • throw statement:

    • Allows you to create and throw a custom error or any other value. When an error is thrown, the normal flow of execution stops, and the JavaScript engine looks for a catch block.
    • You can throw built-in Error objects (like Error, TypeError, RangeError) or custom error objects/values.

Syntax:

try {
// Code that might throw an error
} catch (error) {
// Code to execute if an error occurs
console.error(error);
} finally {
// Code that will always execute, regardless of error
}

Example:

function divide(a, b) {
try {
if (b === 0) {
throw new Error("Cannot divide by zero!");
}
return a / b;
} catch (error) {
console.error("Error:", error.message);
return null;
} finally {
console.log("Division attempt finished.");
}
}

console.log(divide(10, 2)); // 5, Division attempt finished.
console.log(divide(10, 0)); // Error: Cannot divide by zero!, Division attempt finished., null

 

19. Event Loop

A fundamental javascript concepts is concurrency model. It ensures that asynchronous operations are handled efficiently without blocking the main thread. It continuously checks the call stack and the message queue, pushing tasks from the queue to the stack when the stack is empty.

The Event Loop is the mechanism that allows JavaScript to perform non-blocking I/O operations despite being single-threaded. When asynchronous tasks (like setTimeout, network requests, DOM events) complete, their callback functions are placed in a “message queue”.

The Event Loop constantly monitors the “call stack” (where synchronous code executes) and, if the call stack is empty, it moves a task from the message queue to the call stack for execution.

Example (Conceptual):

console.log("Script start"); // 1. Sync Code

setTimeout(() => {
console.log("setTimeout 1"); // 5. Macrotask 1 (from Message Queue)
Promise.resolve().then(() => {
console.log("Promise inside setTimeout"); // 6. Microtask (gets priority over setTimeout 2)
});
}, 0); // This 0ms delay actually means "as soon as possible, but after current stack"

Promise.resolve().then(() => {
console.log("Promise 1"); // 3. Microtask 1 (from Microtask Queue)
});

Promise.resolve().then(() => {
console.log("Promise 2"); // 4. Microtask 2 (from Microtask Queue)
});

setTimeout(() => {
console.log("setTimeout 2"); // 7. Macrotask 2 (from Message Queue)
}, 0);

console.log("Script end"); // 2. Sync Code

/* Predicted Output:
Script start
Script end
Promise 1
Promise 2
setTimeout 1
Promise inside setTimeout
setTimeout 2
*/

Explanation of Example:

  1. console.log(“Script start”); executes immediately.
  2. setTimeout(() => { console.log(“setTimeout 1”); … }, 0); is sent to the Web API. Its callback is placed in the Message Queue after 0ms.
  3. Promise.resolve().then(() => { console.log(“Promise 1”); }); is also handled. Its callback is placed in the Microtask Queue.
  4. Promise.resolve().then(() => { console.log(“Promise 2”); }); is handled. Its callback is placed in the Microtask Queue after Promise 1’s callback.
  5. setTimeout(() => { console.log(“setTimeout 2”); }, 0); is sent to the Web API. Its callback is placed in the Message Queue after setTimeout 1’s callback.
  6. console.log(“Script end”); executes immediately.
  7. Call Stack is now empty.
  8. Event Loop checks Microtask Queue.
    • Pushes console.log(“Promise 1”) to Call Stack. Executes.
    • Pushes console.log(“Promise 2”) to Call Stack. Executes.
  9. Microtask Queue is now empty.
  10. Event Loop checks Message Queue.
    • Pushes setTimeout 1’s callback to Call Stack.
    • Inside setTimeout 1’s callback: console.log(“setTimeout 1”) executes.
    • Promise.resolve().then(() => { console.log(“Promise inside setTimeout”); }); is encountered. Its callback is immediately placed back into the Microtask Queue (because it’s a promise callback).
    • setTimeout 1’s callback finishes. Call Stack is empty.
  11. Event Loop checks Microtask Queue again (because Call Stack just became empty).
    • Pushes console.log(“Promise inside setTimeout”) to Call Stack. Executes.
  12. Microtask Queue is empty again.
  13. Event Loop checks Message Queue.
    • Pushes setTimeout 2’s callback to Call Stack. Executes console.log(“setTimeout 2”).
  14. All tasks finished.

This sequence perfectly illustrates the priority of microtasks over macrotasks within a single Event Loop cycle.

20. DOM Manipulation

The Document Object Model (DOM) is a programming interface for web documents. It represents the page structure as a tree of objects, and JavaScript can interact with this tree to dynamically change the content, structure, and style of a web page.

DOM manipulation is how JavaScript makes web pages interactive and dynamic. Without it, web pages would be static.

Key Concepts:

  • Document Object: The entry point to the DOM, typically document (e.g., document.getElementById()). It represents the entire HTML page.
  • Nodes: Everything in an HTML document is a node. There are different types of nodes:
    • Element nodes: Represent HTML tags (e.g., <div>, <p>, <h1>).
    • Text nodes: Represent the actual text content within elements.
    • Attribute nodes: Represent attributes of elements (e.g., href in <a href=”…”>).
  • Tree Structure: The DOM is organized hierarchically like a family tree, with parent, child, and sibling relationships between nodes.
  • CRUD Operations (Create, Read, Update, Delete): DOM manipulation essentially involves these operations on elements.

Common DOM Manipulation Techniques:

  1. Selecting Elements:

    • document.getElementById(‘id’): Selects a single element by its unique ID.
    • document.querySelector(‘CSS_selector’): Selects the first element that matches a specified CSS selector.
    • document.querySelectorAll(‘CSS_selector’): Selects all elements that match a specified CSS selector, returning a NodeList (which is like an array but not a true array).
    • document.getElementsByClassName(‘class’): Selects all elements with a given class name, returning an HTMLCollection.
    • document.getElementsByTagName(‘tag’): Selects all elements with a given tag name (e.g., p, div), returning an HTMLCollection.
    • Note: querySelector and querySelectorAll are generally preferred due to their flexibility with CSS selectors.
  2. Modifying Content:

    • element.textContent: Gets or sets the text content of an element (no HTML parsing).
    • element.innerHTML: Gets or sets the HTML content within an element (parses HTML string). Be cautious of XSS vulnerabilities when using innerHTML with untrusted input.
  3. Modifying Attributes:

    • element.setAttribute(‘name’, ‘value’): Adds or updates an attribute.
    • element.getAttribute(‘name’): Gets the value of an attribute.
    • element.removeAttribute(‘name’): Removes an attribute.
    • element.classList.add(‘class’), element.classList.remove(‘class’), element.classList.toggle(‘class’): Easier way to manage CSS classes.
  4. Styling Elements:
  • * `element.style.property = ‘value’`: Directly applies inline CSS styles. (e.g., `element.style.color = ‘red’;`). Often better to toggle CSS classes for more complex styling.
  1. Creating and Appending/Removing Elements:

    • document.createElement(‘tagName’): Creates a new HTML element.
    • document.createTextNode(‘text’): Creates a new text node.
    • parentElement.appendChild(childElement): Adds a child element to the end of a parent.
    • parentElement.prepend(childElement): Adds a child element to the beginning of a parent (ES6+).
    • parentElement.insertBefore(newElement, referenceElement): Inserts a new element before a specified reference element.
    • element.remove(): Removes the element from the DOM.
    • parentElement.removeChild(childElement): Removes a specified child element from its parent.
  2. Event Handling:

    • element.addEventListener(‘event’, handlerFunction): Attaches an event listener to an element. event can be ‘click’, ‘mouseover’, ‘submit’, etc.

Example (HTML and JavaScript):

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM Manipulation Example</title>
</head>
<body>
<h1 id="myHeading">Hello, DOM!</h1>
<button id="myButton">Click Me</button>
<div id="container"></div>

<script>
// Select elements
const heading = document.getElementById('myHeading');
const button = document.getElementById('myButton');
const container = document.getElementById('container');

// Modify content
heading.textContent = "JavaScript is Awesome!";

// Add a class for styling (e.g., in a CSS file)
heading.classList.add('highlight');

// Create a new element
const newParagraph = document.createElement('p');
newParagraph.textContent = "This paragraph was added dynamically.";
newParagraph.style.color = "blue"; // Inline style

// Append the new element
container.appendChild(newParagraph);

// Add an event listener
button.addEventListener('click', () => {
alert('Button clicked!');
heading.style.color = "red"; // Change heading color on click
});
</script>
</body>
</html>

Conclusion

Mastering these 20 JavaScript concepts will provide you with a robust foundation for building sophisticated and efficient javascript applications. From understanding the basics of variables and data types to embracing advanced topics like asynchronous programming and prototypal inheritance, each JavaScript concepts plays a vital role in becoming a proficient JavaScript developer.

Continuously practice and apply these javascript concepts in your projects to solidify your understanding and unlock the full potential of JavaScript.

Write a Reply or Comment

Your email address will not be published. Required fields are marked *