Building Blocks of JavaScript: Mastering Variables, Data Types, and Control Flow

Building Blocks of JavaScript: Mastering Variables, Data Types, and Control Flow

A Beginner's Guide to JavaScript Foundations

Variables & Scopes

In JavaScript, variables are used to store and manipulate data. They act as named storage for values that can be reused or modified throughout a program. Understanding variables is crucial since they are the building blocks for any JavaScript application.

1. Declaring Variables

JavaScript provides three ways to declare variables: var, let, and const. Each has distinct characteristics regarding scope, reassignability, and hoisting.

a. var Declaration

  • Introduced in early versions of JavaScript (ES1).

  • Function-scoped: If a variable is declared inside a function, it can only be accessed within that function. If declared outside, it’s attached to the global object.

  • Hoisting: var declarations are hoisted to the top of their scope (function or global), but their initialization remains in place.

      console.log(x); // undefined (hoisted, but not initialized)
      var x = 10;
      console.log(x); // 10
    
  • Re-declaration and reassignment: var allows re-declaration of the same variable and reassignment of values.

      var a = 5;
      var a = 10; // No error, allowed in non-strict mode
      a = 20; // Reassignment is also allowe
    

b. let Declaration

  • Introduced in ES6 (2015).

  • Block-scoped: Variables declared with let are limited to the block (a pair of curly braces {}) in which they are defined.

  • Hoisting: let is also hoisted, but it’s in a “temporal dead zone” from the top of its scope until the line of declaration. Accessing it before declaration results in a ReferenceError.

      console.log(y); // ReferenceError: Cannot access 'y' before initialization
      let y = 10;
      console.log(y); // 10
    
  • Re-declaration: let does not allow re-declaring the same variable in the same scope but allows reassignment.

      let b = 5;
      // let b = 10; // SyntaxError: Identifier 'b' has already been declared
      b = 10; // Reassignment is allowed
    

c. const Declaration

  • Introduced in ES6 (2015).

  • Block-scoped like let.

  • Constant: Variables declared with const must be initialized during declaration and cannot be reassigned.

  • Hoisting: Like let, const is hoisted but exists in the temporal dead zone until initialized.

      const z = 20;
      // z = 25; // TypeError: Assignment to constant variable.
    
  • Immutability: While the binding is constant, the data stored in objects or arrays declared with const can still be modified (mutability within the object or array).

      const arr = [1, 2, 3];
      arr.push(4); // Allowed, because the array's content can change
      console.log(arr); // [1, 2, 3, 4]
    
      const obj = {name: "Alice"};
      obj.name = "Bob"; // Allowed, because object properties can change
      console.log(obj); // {name: "Bob"}
    

2. Variable Scope

Scope determines the accessibility of variables in different parts of the code.

a. Global Scope

  • Variables declared outside any function or block are in the global scope.

  • In browsers, global variables are added as properties of the window object.

      var globalVar = 100;
      console.log(window.globalVar); // 100
    
  • Using let and const to declare global variables doesn’t attach them to the window object.

      let globalLet = 50;
      console.log(window.globalLet); // undefined
    

b. Function Scope (for var)

  • Variables declared with var inside a function are limited to that function.

      function test() {
        var localVar = 10;
        console.log(localVar); // 10
      }
      console.log(localVar); // ReferenceError: localVar is not defined
    

c. Block Scope (for let and const)

  • Variables declared with let or const inside blocks ({}) are limited to that block.

  • This makes let and const better suited for block-scoped operations like for loops and conditional statements.

      if (true) {
        let blockScoped = "I'm block scoped!";
        const alsoBlockScoped = "Me too!";
      }
      console.log(blockScoped); // ReferenceError
    

3. Hoisting

  • Hoisting is JavaScript’s behavior of moving declarations to the top of their scope before code execution.

  • var: Hoisted with an undefined value until the point of assignment.

  • let and const: Hoisted but not initialized, leading to the temporal dead zone (where accessing them before declaration throws an error).

      console.log(a); // undefined (due to hoisting)
      var a = 10;
    
      console.log(b); // ReferenceError: Cannot access 'b' before initialization
      let b = 10;
    

4. Reassigning and Re-declaring Variables

  • Reassignment is changing the value of a variable after it has been declared.

  • Re-declaration is declaring a variable again within the same scope.

  • var: Allows both reassignment and re-declaration.

  • let: Allows reassignment but not re-declaration in the same scope.

  • const: Allows neither reassignment nor re-declaration.


Conclusion

Understanding how to declare, scope, and use variables in JavaScript is crucial for writing clean, maintainable, and bug-free code. The introduction of let and const in ES6 has provided safer and more predictable variable behavior compared to the older var.


Data Types

A data type is an abstract idea that helps us group data according to its value and the whole set of operations possible on that data.

JavaScript comes equipped with 7 primitive data types: undefined, null, numbers, strings, Booleans, Symbols, and BigInt.

JavaScript divides data essentially into two main categories: primitives and objects (a composite type).

A primitive is one of the simplest forms of data. The key characteristic of primitives is that they don't have any properties/methods attached to them.

An object is a value composed of primitives. An object, unlike a primitive, can have properties/methods attached to it.

An object is sometimes also referred to as a reference. And by that means, the object type is also known as the reference type.

Simply put, if a value isn't a primitive in JavaScript, then it is an object. Apart form the seven primitive data types, everything else is an object in the language.

Primitive Data Types

Primitive data types are immutable and hold a single value. They are:

  1. Number

  2. String

  3. Boolean

  4. Undefined

  5. Null

  6. Symbol (introduced in ES6)

  7. BigInt (introduced in ES2020)

1. Number

The Number type is used for both integers and floating-point numbers (decimals).

  • JavaScript only has one number type, represented as a 64-bit floating-point number (IEEE 754 standard).

Examples:

const intNum = 42;        // Integer
const floatNum = 3.14;    // Floating-point
const negNum = -100;      // Negative number
const exponent = 2e5;     // 2 * 10^5 = 200000

Special Numbers:

  • Infinity: Positive and negative infinity.

      console.log(1 / 0); // Infinity
      console.log(-1 / 0); // -Infinity
    
  • NaN: Stands for "Not-a-Number" and occurs when an invalid mathematical operation is performed.

      console.log(0 / 0); // NaN
    

2. String

The String type represents a sequence of characters (text). Strings are enclosed in single quotes ('...'), double quotes ("..."), or template literals (...).

Examples:

const str1 = 'Hello';
const str2 = "World";
const str3 = `Hello, ${str2}!`; // Template literals with interpolation
console.log(str3); // Output: Hello, World!

3. Boolean

The Boolean type has two possible values: true or false. Booleans are commonly used in logical operations and conditional statements.

Examples:

const isTrue = true;
const isFalse = false;
console.log(5 > 3); // true
console.log(5 < 3); // false

4. Undefined

The undefined type represents a variable that has been declared but not assigned a value yet. It’s automatically assigned by JavaScript to any variable without a defined value.

Example:

let notAssigned;
console.log(notAssigned); // undefine

5. Null

null represents the intentional absence of any object value. It is different from undefined because undefined indicates the lack of assignment, while null indicates an intentional "empty" value.

Example:

let emptyValue = null;
console.log(emptyValue); // null

6. Symbol (ES6)

Symbol is a unique and immutable data type used to create unique identifiers for object properties. Even if two symbols have the same description, they are guaranteed to be unique.

Example:

const sym1 = Symbol('description');
const sym2 = Symbol('description');
console.log(sym1 === sym2); // false, they are unique

7. BigInt (ES2020)

BigInt is a numeric type that can represent integers with arbitrary precision. It is used when dealing with very large integers that exceed the safe integer limit for the Number type (2^53 - 1).

Example:

const bigInt1 = 1234567890123456789012345678901234567890n; // BigInt literal with 'n'
const bigInt2 = BigInt(123456789012345678901234567890);    // Using BigInt constructor
console.log(bigInt1 + bigInt2); // BigInt arithmetic

Non-Primitive Data Type

Objects

Objects are the most complex and versatile data type in JavaScript. They are a collection of properties and methods, where each property is a key-value pair. Objects can hold any type of value, including other objects, arrays, functions, etc.

Examples of Objects:

Plain Object

const person = {
  name: "Alice",
  age: 25,
  greet: function() {
    console.log("Hello, " + this.name);
  }
};
person.greet(); // Output: Hello, Alice

Array (a type of Object)

Arrays are objects that store ordered collections of values (of any type).

const arr = [1, 2, 3, "four", { five: 5 }];
console.log(arr[3]); // Output: "four"

Function (a type of Object)

Functions in JavaScript are also objects and can have properties. However, their primary purpose is to define reusable blocks of code.

function add(a, b) {
  return a + b;
}
console.log(add(5, 3)); // Output:

Dynamic Typing

JavaScript is a dynamically typed language, which means that variables do not have types. Instead, the values they hold have types, and you can reassign variables to hold values of different types.

Example:

let dynamicVar = 42;   // Number type
dynamicVar = "Hello";  // Now it's a String
console.log(typeof dynamicVar); // Output: "string"

Type Conversion

1. Implicit Type Coercion

JavaScript automatically converts values to the expected type in certain operations. This is called implicit type coercion.

Example:

console.log(5 + "5"); // Output: "55" (Number is coerced to String)
console.log("5" * 2); // Output: 10 (String is coerced to Number)

2. Explicit Type Conversion

You can explicitly convert values between types using methods like String(), Number(), and Boolean().

Example:

const num = 123;
const str = String(num);  // Converts Number to String
console.log(typeof str);  // Output: "string"

const strToNum = Number("123");  // Converts String to Number
console.log(typeof strToNum);  // Output: "number"

Type Checking

You can check the type of a value using the typeof operator.

Examples:

console.log(typeof 42);           // "number"
console.log(typeof "Hello");      // "string"
console.log(typeof true);         // "boolean"
console.log(typeof undefined);    // "undefined"
console.log(typeof null);         // "object" (this is a known quirk in JavaScript)
console.log(typeof Symbol());     // "symbol"
console.log(typeof 123n);         // "bigint"
console.log(typeof {});           // "object"
console.log(typeof function(){}); // "function"

Note that null is considered an object due to a historical bug in JavaScript. However, you can distinguish null from objects using a more specific check:

console.log(null === null);   // true
console.log(null === {});     // false

<aside> ℹ️

How can a primitive in JavaScript have a property on it? Via autoboxing.

Autoboxing is simply when JavaScript automatically boxes, i.e. wraps, a primitive value into an object based on its corresponding wrapper class.

Essentially, excluding undefined and null, JavaScript defines wrapper classes (sometimes also referred to as wrapper types) for all primitive types, used to denote corresponding objects of those types. The nomenclature of these objects is also pretty intuitive.

So, for example, suppose that str is a variable holding a string. Accessing a property on str, like str.length would autobox str into a String object and then access the property on the object before deleting the object.

How exactly autoboxing is performed is completely implementation-dependent. But it's definitely a thing in JavaScript, and likewise, worth knowing.

</aside>


Conclusion

  • Primitive Types: Number, String, Boolean, Undefined, Null, Symbol, and BigInt.

  • Non-Primitive Types: Objects (including Arrays, Functions).

  • JavaScript is dynamically typed, and values can be implicitly or explicitly converted between types.

  • Type checking is done using typeof, though some quirks exist (like null being reported as an object).

Understanding these types is crucial for mastering JavaScript's behavior and interactions, especially in terms of memory, performance, and type coercion.


Operators

In JavaScript, operators are used to perform operations on values (variables, constants, literals, or expressions). They can be classified into different categories based on their behavior.

1. Arithmetic Operators

Arithmetic operators are used to perform mathematical operations.

OperatorDescriptionExample
+Addition5 + 38
-Subtraction5 - 32
*Multiplication5 * 315
/Division9 / 33
%Modulus (Remainder)10 % 31
++Increment (adds 1)let a = 5; a++6
--Decrement (subtracts 1)let a = 5; a--4

Unary Plus and Minus

  • Unary Plus (+): Converts its operand into a number (if it’s not already a number).

      +"5";   // 5
      +"true"; // NaN (Not a Number)
    
  • Unary Minus (``): Converts its operand into a number and negates it.

      -"5";   // -5
    

2. Assignment Operators

Assignment operators assign values to variables.

OperatorDescriptionExample
=Assignx = 5
+=Add and assignx += 3 (equivalent to x = x + 3)
-=Subtract and assignx -= 2
*=Multiply and assignx *= 4
/=Divide and assignx /= 2
%=Modulus and assignx %= 3

3. Comparison Operators

Comparison operators compare two values and return a boolean (true or false).

OperatorDescriptionExample
==Equal to (loose equality)5 == '5'true
===Strict equal to (checks type and value)5 === '5'false
!=Not equal to (loose inequality)5 != '6'true
!==Strict not equal to (checks type and value)5 !== '5'true
>Greater than10 > 5true
<Less than10 < 5false
>=Greater than or equal to5 >= 5true
<=Less than or equal to5 <= 10true

Loose vs. Strict Equality

  • == performs type coercion (converts operands to the same type before comparing).

      '5' == 5;  // true
      true == 1; // true
    
  • === (strict equality) compares both the value and the type.

      '5' === 5;  // false
      true === 1; // false
    

4. Logical Operators

Logical operators are used for logical operations, primarily with boolean values.

OperatorDescriptionExample
&&Logical AND (returns true if both are true)true && falsefalse
``
!Logical NOT (inverts the value)!truefalse

Short-Circuit Evaluation

  • &&: Stops evaluation as soon as the first false operand is encountered.

      false && (expression);  // expression is not evaluated
    
  • ||: Stops evaluation as soon as the first true operand is encountered.

      true || (expression);  // expression is not evaluated
    

5. Bitwise Operators

Bitwise operators work on the binary (bit) level of numbers.

OperatorDescriptionExample
&AND5 & 11
``OR
^XOR5 ^ 14
~NOT~5-6
<<Left shift5 << 110
>>Right shift5 >> 12
>>>Zero-fill right shift5 >>> 12

6. Ternary (Conditional) Operator

The ternary operator is a shorthand for if-else conditions. It has three operands: a condition followed by a question mark (?), then an expression for the true case and one for the false case.

let age = 18;
let canVote = (age >= 18) ? "Yes" : "No";
console.log(canVote); // "Yes"

Control Flow

In JavaScript, control flow refers to the order in which individual statements, instructions, or function calls are executed in a program. By default, the control flow in JavaScript follows a top-to-bottom, left-to-right approach, meaning that statements are executed one after another in the order they are written. However, control flow can be altered using control flow statements to make decisions, repeat blocks of code, or jump to different parts of the code.

Conditional Statements

Conditional statements allow the code to execute different paths based on certain conditions.

a. if Statement

The if statement executes a block of code if a specified condition is true.

let age = 18;
if (age >= 18) {
  console.log("You are eligible to vote.");
}

b. if-else Statement

The if-else statement allows you to execute one block of code if the condition is true, and another block if the condition is false.

let age = 16;
if (age >= 18) {
  console.log("You are eligible to vote.");
} else {
  console.log("You are not eligible to vote.");
}

c. else if Statement

The else if statement is used when you need to check multiple conditions. The first if block that evaluates to true gets executed, and the rest are ignored.

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

d. Ternary (Conditional) Operator

The ternary operator is a shorthand way to write simple if-else statements. It uses the syntax condition ? expressionIfTrue : expressionIfFalse.

let age = 18;
let canVote = (age >= 18) ? "Yes" : "No";
console.log(canVote); // "Yes"

e. switch Statement

The switch statement is used to perform different actions based on different values of a single expression. It's often an alternative to else if chains when dealing with multiple possible values for a variable.

let day = 3;
switch (day) {
  case 1:
    console.log("Monday");
    break;
  case 2:
    console.log("Tuesday");
    break;
  case 3:
    console.log("Wednesday");
    break;
  default:
    console.log("Invalid day");
}
  • break: The break statement prevents the code from falling through to the next case.

  • default: The default block is executed if no cases match the expression.


Conclusion

Control flow in JavaScript determines how the program moves from one statement to another. Using conditional statements, loops, error handling, and asynchronous constructs allows you to manage the order and conditions under which code is executed. This is essential for writing logical, efficient, and robust JavaScript programs.

Loops and Iterations

In JavaScript, loops and iterations are fundamental concepts used to repeatedly execute a block of code. This is useful when you need to perform repetitive tasks, such as processing items in an array or running a set of instructions until a specific condition is met.

1. Types of Loops in JavaScript

JavaScript provides several types of loops to iterate over data structures or execute code multiple times:


1.1 for Loop

The for loop is the most commonly used loop in JavaScript. It consists of three expressions:

  • Initialization: Initializes a variable (e.g., let i = 0).

  • Condition: Sets the condition that must be true for the loop to continue (e.g., i < 10).

  • Update: Updates the loop variable after each iteration (e.g., i++).

Syntax:

for (initialization; condition; update) {
  // code block to be executed
}

Example:

for (let i = 0; i < 5; i++) {
  console.log(i);  // Output: 0, 1, 2, 3, 4
}
  • Flow:

    1. The initialization step (let i = 0) is executed first.

    2. Then, the condition (i < 5) is evaluated; if true, the code block is executed.

    3. After the block is executed, the update step (i++) increments the variable.

    4. The loop repeats until the condition becomes false.


1.2 while Loop

The while loop repeats a block of code as long as the specified condition evaluates to true.

Syntax:

while (condition) {
  // code block to be executed
}

Example:

let i = 0;
while (i < 5) {
  console.log(i);  // Output: 0, 1, 2, 3, 4
  i++;
}
  • Flow:

    • First, the condition (i < 5) is checked.

    • If it’s true, the code block is executed.

    • After the block is executed, control goes back to check the condition again.

    • The loop continues until the condition becomes false.


1.3 do-while Loop

The do-while loop is similar to the while loop, except that the code block is executed at least once, even if the condition is false at the beginning.

Syntax:

do {
  // code block to be executed
} while (condition)

Example:

let i = 0;
do {
  console.log(i);  // Output: 0, 1, 2, 3, 4
  i++;
} while (i < 5);
  • Flow:

    • The code block is executed before the condition is checked.

    • Then the condition (i < 5) is evaluated. If true, the loop continues; otherwise, it stops.


1.4 for-in Loop

The for-in loop is used to iterate over the enumerable properties (keys) of an object. It is often used for objects rather than arrays.

Syntax:

for (key in object) {
  // code block to be executed
}

Example:

let person = {name: "John", age: 25, city: "New York"};
for (let key in person) {
  console.log(key + ": " + person[key]);  // Output: "name: John", "age: 25", "city: New York"
}
  • Flow:

    • The loop iterates over all enumerable properties in the object (person), assigning the property name (key) to the variable (key).

    • For each iteration, you can access the value using object[key].

Note: It's generally not used for arrays because it iterates over all properties, including non-numeric ones, which can lead to unexpected results.


1.5 for-of Loop

The for-of loop is used to iterate over iterable objects, such as arrays, strings, maps, sets, and other collections.

Syntax:

for (variable of iterable) {
  // code block to be executed
}

Example:

let numbers = [10, 20, 30];
for (let number of numbers) {
  console.log(number);  // Output: 10, 20, 30
}
  • Flow:

    • The loop iterates over the values of the iterable (e.g., array) and assigns each value to the variable (number) in each iteration.

Note: The for-of loop is generally preferred over for-in for arrays.


2. Loop Control Statements

Sometimes, you might want to modify the normal flow of loops. JavaScript provides control statements like break and continue to manage this.

2.1 break Statement

The break statement is used to exit (terminate) a loop prematurely, even if the loop’s condition hasn’t been met.

Example:

for (let i = 0; i < 5; i++) {
  if (i === 3) {
    break;  // Loop stops when i is 3
  }
  console.log(i);  // Output: 0, 1, 2
  • Flow:

    • When the condition (i === 3) is met, the break statement terminates the loop.

2.2 continue Statement

The continue statement skips the current iteration and moves to the next iteration of the loop.

Example:

for (let i = 0; i < 5; i++) {
  if (i === 2) {
    continue;  // Skip the iteration when i is 2
  }
  console.log(i);  // Output: 0, 1, 3, 4
}
  • Flow:

    • When the condition (i === 2) is met, the continue statement skips the rest of the code for that iteration and moves to the next iteration.

3. Iteration with Arrays and Objects

3.1 Iterating Over Arrays

  • for Loop: A common approach to iterate over arrays.

      let arr = [1, 2, 3, 4];
      for (let i = 0; i < arr.length; i++) {
        console.log(arr[i]);
      }
    
  • for-of Loop: Modern and simpler to use than for-in or for.

      let arr = [1, 2, 3, 4];
      for (let value of arr) {
        console.log(value);
      }
    
  • Array Methods: Functions like forEach(), map(), filter(), etc., are also used for iteration.

      let arr = [1, 2, 3, 4];
      arr.forEach(value => console.log(value));  // Output: 1, 2, 3, 4
    

3.2 Iterating Over Objects

While objects are not iterable like arrays, you can use for-in or methods like Object.keys() or Object.entries() to iterate over them.

  • for-in Loop:

      let obj = {name: "Alice", age: 25};
      for (let key in obj) {
        console.log(key, obj[key]);
      }
    
  • Object.keys(), Object.values(), and Object.entries():

      let obj = {name: "Alice", age: 25};
      Object.keys(obj).forEach(key => console.log(key, obj[key]));  // Output: "name Alice", "age 25"
    

Conclusion

  • Loops in JavaScript provide mechanisms to repeat actions until a certain condition is met or a specific task is completed.

  • Different loop types like for, while, do-while, for-in, and for-of are used in different situations, depending on the data structure and the use case.

  • Control statements like break and continue can modify the flow of loops by terminating or skipping iterations.