< / >
Published on

Core JavaScript Concepts

Table of Contents

Variables in JavaScript

Variables are used to store data values. JavaScript has three ways to declare a variable: var, let, and const.

var

  • var was the primary way to declare variables in JavaScript, and it is still widely used.
  • Variables declared with var have function-level scope, meaning they are accessible within the function they are declared in, or globally if declared outside of any function.
  • var variables can be redeclared and updated throughout the program.
  • var declarations are hoisted to the top of their scope during runtime, which can sometimes lead to unexpected behavior.

Example:

var x = 100
console.log(x) // Output: 100

function exampleVar() {
  if (true) {
    var x = 100
  }
  console.log(x) // Output: 100
}

exampleVar()

let

  • Introduced in ECMAScript 6 (ES6), let provides a more predictable way to declare variables compared to var.
  • Variables declared with let have block-level scope, meaning they are accessible only within the block they are declared in.
  • Unlike var, let variables cannot be redeclared in the same scope, but they can be updated.
  • let declarations are not hoisted to the top of their scope, which helps prevent certain types of bugs.

Example:

let x = 100
if(true){
    lex x = 50
    console.log(x) // Output: 50
}
console.log(x) // Output: 100

function exampleLet() {
  if (true) {
    let y = 100
    console.log(y); // Output: 100
  }
  // console.log(y); // Error: y is not defined
}

exampleLet()

const

  • Introduced in ES6, const is used to declare variables that cannot be reassigned.
  • const variables have block-level scope like let.
  • While the value of a const variable cannot be reassigned, if it's an object or array, its properties or elements can still be modified.
  • const declarations are not hoisted and must be initialized with a value at the time of declaration.

Example:

const x = 100
if (true) {
  const x = 50
  console.log(x) // Output: 50
}
console.log(x) // Output: 100

function exampleConst() {
  if (true) {
    const y = 100
    console.log(y) // Output: 100
    // y = 50; // Error: Assignment to constant variable
  }
  // console.log(y); // Error: y is not defined
}

exampleConst()

Scope in JavaScript

Scope refers to the visibility and accessibility of variables in different parts of your code. In JavaScript, there are mainly two types of scope:

  1. Global Scope: Variables declared outside of any function or block have global scope, meaning they are accessible from anywhere within the JavaScript code.

  2. Local Scope: Variables declared within a function or block have local scope, meaning they are accessible only within that function or block.

Scope with var

Variables declared with var have function-level scope. This means they are accessible throughout the function in which they are declared, including within nested blocks. However, they are not accessible outside of that function.

Example:

function varScopeExample() {
  if (true) {
    var x = 10
    console.log(x) // Output: 10
  }
  console.log(x) // Output: 10
}
varScopeExample()
// console.log(x); // Error: x is not defined

In the above example, x is accessible both inside and outside of the if block because it is declared with var within the function varScopeExample(). However, it is not accessible outside of the function.

Scope with let and const

Variables declared with let and const have block-level scope. This means they are accessible only within the block in which they are declared, including nested blocks, but not outside of that block.

Example:

function letConstScopeExample() {
  if (true) {
    let y = 20
    const z = 30
    console.log(y) // Output: 20
    console.log(z) // Output: 30
  }
  // console.log(y); // Error: y is not defined
  // console.log(z); // Error: z is not defined
}
letConstScopeExample()

In this example, y and z are declared within the if block using let and const, respectively. Therefore, they are only accessible within that block. Attempting to access them outside of the block would result in an error.

Summary

  • var variables have function-level scope.
  • let and const variables have block-level scope.
  • Variables declared with var are hoisted to the top of their function scope, while let and const variables are not hoisted.
  • Using let and const provides more predictable scoping behavior and helps prevent certain types of bugs, especially in larger codebases.

Hoisting

Hoisting is a JavaScript mechanism where variable and function declarations are moved to the top of their containing scope during the compile phase before the code execution. This means that no matter where functions and variables are declared within a scope, they are moved to the top of their scope, allowing them to be used before they are actually declared in the code.

Hoisting with var

When variables are declared using var, they are hoisted to the top of their function scope. However, only the variable declaration is hoisted, not the initialization.

console.log(x) // Output: undefined
var x = 10

The above code is interpreted by JavaScript as:

var x
console.log(x) // Output: undefined
x = 10

Hoisting with let and const

Variables declared using let and const are also hoisted to the top of their block scope, but they are not initialized until the actual declaration is encountered in the code. This is known as the temporal dead zone.

// console.log(y); // Error: Cannot access 'y' before initialization
let y = 20

In the above code, even though y is declared using let, trying to access it before its declaration results in an error because it is not yet initialized.

Hoisting Considerations

  • var are hoisted and initialized with undefined, variables declared with let and const are not initialized until their actual declaration, leading to a temporal dead zone where accessing them results in a ReferenceError.
  • It's generally recommended to declare variables at the top of their scope to improve code readability and avoid potential hoisting-related issues.

Hoisting with Functions

Function declarations are also hoisted to the top of their containing scope.

foo() // Output: "Hello, world!"
function foo() {
  console.log('Hello, world!')
}

In the above code, the function foo is called before its declaration, but it still works because function declarations are hoisted to the top.

Hoisting with Function Expressions

Function expressions are not hoisted in the same way as function declarations. Only the variable declaration is hoisted, not the function initialization.

// console.log(add(2, 3)); // Error: add is not a function
var add = function (a, b) {
  return a + b
}
console.log(add(2, 3)) // Output: 5

In the above code, attempting to call add before its declaration results in an error because the function expression is not hoisted.

Hoisting with Class Declarations

Class declarations are hoisted like function declarations, meaning they are moved to the top of their scope.

const car = new Car() // Output: "I am a car!"
class Car {
  constructor() {
    console.log('I am a car!')
  }
}

In the above code, the Car class is accessed before its declaration, but it still works because class declarations are hoisted to the top.

Hoisting Caveats

  1. Arrow Functions: Arrow functions are not hoisted because they are essentially function expressions. Therefore, trying to access them before their declaration will result in a reference error.
// console.log(add(2, 3)); // Error: add is not defined
const add = (a, b) => a + b
console.log(add(2, 3)) // Output: 5
  1. Variable Initialization:While variables declared with var are initialized with undefined, and variables declared with let and const remain uninitialized until their declaration, it's important to note that the variable declaration itself is hoisted in all cases.
console.log(x) // Output: undefined
var x

Temporal Dead Zone (TDZ)

The Temporal Dead Zone (TDZ) is a period in JavaScript code where a variable declared with let or const exists but cannot be accessed or referenced before its declaration. This happens because variables declared with let and const are hoisted to the top of their containing block scope, but they are not initialized until the actual declaration statement is reached. Accessing such variables during the TDZ results in a ReferenceError.

// No TDZ Example
let outside = 'outside TDZ'

{
  console.log(outside) // Output: 'outside TDZ'
  // The variable `outside` is accessible here because it's outside of any TDZ.
}

// TDZ Example
{
  console.log(inside) // Output: ReferenceError: Cannot access 'inside' before initialization
  let inside = 'inside TDZ'
}
  • The variable outside is declared outside of any block and is accessible both inside and outside of the block without any issues.
  • The variable inside is declared using let within a block. When JavaScript reaches the console.log(inside) statement, it recognizes that inside has been declared but not yet initialized.
  • Since inside is still in its TDZ, attempting to access it results in a ReferenceError.
let condition = true

if (condition) {
  console.log(x) // Output: ReferenceError: Cannot access 'x' before initialization
  let x = 'inside if'
} else {
  let y = 'inside else'
  console.log(y) // Output: undefined
}
  • If the condition is true, JavaScript will attempt to access x before its declaration inside the if block, resulting in a ReferenceError due to the TDZ.
  • If the condition is false, y is declared and initialized within the else block. However, since y is declared using let, it is not hoisted outside of the else block, so trying to access it outside of the block results in undefined.

Data Types (string, number, boolean, object, array)

  • string: Represents textual data, enclosed within single or double quotes.
let greeting = 'Hello, World!'
  • number: Represents numeric data, both integers and floating-point numbers.
let age = 25
let pi = 3.14
  • boolean: Represents a logical value, either true or false.
let isLogged = true
let hasPermission = false
  • object: Represents a collection of key-value pairs. Keys are strings, and values can be of any data type.
let person = {
  name: 'Rajnish',
  age: 30,
  isStudent: false,
}
  • array: Represents an ordered collection of elements, which can be of any data type.
let colors = ['red', 'green', 'blue']
let numbers = [1, 2, 3, 4, 5]

Operators (arithmetic, comparison, logical)

  • Arithmetic Operators: Used to perform arithmetic operations like addition, subtraction, multiplication, division, etc.
let a = 110
let b = 50
let sum = a + b // Addition
let difference = a - b // Subtraction
let product = a * b // Multiplication
let quotient = a / b // Division
  • Comparison Operators: Used to compare values and return a boolean result.
let x = 10
let y = 5
console.log(x > y) // Output: true
console.log(x === y) // Output: false
  • Logical Operators: Used to combine or manipulate boolean values.
let isLogged = true
let isAdmin = false
console.log(isLogged && isAdmin) // Output: false
console.log(isLogged || isAdmin) // Output: true

Control Flow (if-else statements, switch statements, loops)

  • if-else statements: Used for conditional execution of code.
let age = 18
if (age >= 18) {
  console.log('You are an adult.')
} else {
  console.log('You are a minor.')
}
  • switch statements: Used to perform different actions based on different conditions.
let day = 'Monday'
switch (day) {
  case 'Monday':
    console.log('Today is Monday.')
    break
  case 'Tuesday':
    console.log('Today is Tuesday.')
    break
  default:
    console.log("It's not Monday or Tuesday.")
}
  • Loops: Used to execute a block of code repeatedly.
// for loop:
for (let i = 0; i < 5; i++) {
  console.log(i)
}

// while loop:
let i = 0
while (i < 5) {
  console.log(i)
  i++
}
// do-while loop:
let i = 0
do {
  console.log(i)
  i++
} while (i < 5)