jsnotes

Topic 001 : Arrays, Arrow function, Object, Map, Filter, Reduce, Mutability and Immutability, Destructuring

Topic 001: Arrays, Arrow function, Object, Map, Filter, Reduce, Mutability and Immutability, Destructuring

Arrays

Arrays in JavaScript are a fundamental data structure used to store multiple values in a single variable. They allow you to group together elements of the same type or different types and access them using indices. Here’s a detailed explanation of arrays in JavaScript:

  1. Declaration and Initialization: Arrays in JavaScript can be declared and initialized in several ways:

    // Method 1: Using array literal
    let myArray = []; // Empty array
    let numbers = [1, 2, 3, 4, 5]; // Array with elements
    
    // Method 2: Using the Array constructor
    let colors = new Array(); // Empty array
    let fruits = new Array("Apple", "Banana", "Orange"); // Array with elements
    
  2. Accessing Elements: Array elements are accessed using zero-based indices.

    let fruits = ["Apple", "Banana", "Orange"];
    console.log(fruits[0]); // Output: "Apple"
    console.log(fruits[1]); // Output: "Banana"
    
  3. Properties and Methods: Arrays in JavaScript come with various properties and methods to manipulate them:

    • length: Property that returns the number of elements in the array.
    • push(): Method to add one or more elements to the end of an array.
    • pop(): Method to remove the last element from an array and return it.
    • shift(): Method to remove the first element from an array and return it.
    • unshift(): Method to add one or more elements to the beginning of an array.
    • splice(): Method to add or remove elements from an array at a specific index.
  4. Dynamic Nature: Arrays in JavaScript are dynamic, meaning their size can change dynamically by adding or removing elements.

    let fruits = ["Apple", "Banana", "Orange"];
    fruits.push("Mango"); // Add an element
    fruits.pop(); // Remove the last element
    
  5. Iteration: You can iterate over arrays using loops like for loop, forEach() method, map() method, etc.

    let numbers = [1, 2, 3, 4, 5];
    for (let i = 0; i < numbers.length; i++) {
        console.log(numbers[i]);
    }
    
    // Using forEach method
    numbers.forEach(function(number) {
        console.log(number);
    });
    
  6. Multidimensional Arrays: JavaScript arrays can contain other arrays, allowing you to create multidimensional arrays.

    let matrix = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
    ];
    console.log(matrix[1][2]); // Output: 6
    

Arrays are versatile and widely used in JavaScript for various purposes, such as storing collections of data, implementing data structures, and performing operations on collections. Understanding arrays is crucial for any JavaScript developer.

Objects

In JavaScript, objects are fundamental data structures used to store and organize data. They are collections of key-value pairs where each key is a unique identifier (often referred to as a property) associated with a value. Objects are versatile and can represent various entities, including real-world objects, abstract concepts, functions, and more. Here’s a detailed breakdown of objects in JavaScript:

  1. Key-Value Pairs: As mentioned, objects consist of key-value pairs. The key is always a string (or Symbol, introduced in ES6), and the value can be of any data type, including primitive types (like strings, numbers, booleans, etc.) or even other objects.
  2. Object Literals: One common way to create objects in JavaScript is by using object literals. This involves enclosing key-value pairs within curly braces {}, with each pair separated by a comma. Here’s an example:

     let person = {
         name: "John",
         age: 30,
         isEmployed: true
     };
    
  3. Accessing Properties: You can access properties of an object using either dot notation (object.property) or bracket notation (object['property']). Dot notation is more common and concise, but bracket notation is useful when dealing with dynamic property names or properties with special characters.

     console.log(person.name);  // Output: "John"
     console.log(person['age']); // Output: 30
    
  4. Adding and Modifying Properties: You can add new properties to an object or modify existing ones at any time by simply assigning values to them.

     person.gender = "Male"; // Adding a new property
     person.age = 31;         // Modifying an existing property
    
  5. Deleting Properties: You can remove properties from an object using the delete keyword.

     delete person.isEmployed;
    
  6. Methods: In JavaScript, objects can also contain functions as values. These functions are called methods of the object.

     let person = {
         name: "John",
         sayHello: function() {
             console.log("Hello, my name is " + this.name);
         }
     };
    
     person.sayHello(); // Output: "Hello, my name is John"
    
  7. this Keyword: Within a method, the this keyword refers to the current object instance. It allows methods to access other properties or methods of the same object.
  8. Object Constructor: You can also create objects using constructor functions or ES6 classes.

     // Constructor function
     function Person(name, age) {
         this.name = name;
         this.age = age;
     }
    
     let john = new Person("John", 30);
    
     // ES6 Class
     class Person {
         constructor(name, age) {
             this.name = name;
             this.age = age;
         }
     }
    
     let john = new Person("John", 30);
    
  9. Object Prototypes: In JavaScript, objects can inherit properties and methods from other objects through a mechanism called prototypes. Every JavaScript object has a prototype, which is either null or references another object. Prototypes allow for inheritance in JavaScript, enabling objects to share behavior and reduce redundancy.

These are the key concepts related to objects in JavaScript. Understanding these concepts is crucial for effectively working with data and creating complex applications in JavaScript.

Arrow functions

Arrow functions are a concise way to write anonymous function expressions in JavaScript. They were introduced in ECMAScript 6 (ES6) and provide a more succinct syntax compared to traditional function expressions. Here’s an explanation of arrow functions with details:

  1. Syntax: Arrow functions have a compact syntax compared to regular function expressions. The basic syntax is:

    (parameters) => { statements }
    

    If the function has only one parameter and a single statement, the parentheses around the parameter and curly braces around the statement can be omitted:

    parameter => expression
    
  2. Lexical this Binding: One of the most significant differences between arrow functions and traditional functions is how they handle the this keyword. Arrow functions do not have their own this context. Instead, they inherit this from the surrounding code (lexical scoping). This behavior can be very useful in situations where this might change context in traditional functions, such as when dealing with event handlers or callbacks.
  3. No arguments object: Unlike traditional functions, arrow functions do not have their own arguments object. If you need to access the arguments passed to an arrow function, you can use the rest parameter syntax (...args) or access the arguments object from the surrounding scope.
  4. No new keyword: Arrow functions cannot be used as constructors and cannot be called with the new keyword. Attempting to do so will result in a runtime error.
  5. Implicit return: If the arrow function body consists of a single expression, that expression will be implicitly returned. This allows for more concise code. If the function body requires multiple statements, you need to use explicit return.
  6. Use Cases:

    • Callback functions: Arrow functions are often used as concise callbacks in array methods like map, filter, and reduce.
    • Shorter syntax: They provide a shorter syntax for defining small, inline functions.
    • Avoiding this confusion: Arrow functions can be beneficial in situations where you want to preserve the value of this from the enclosing lexical context.

Example:

// Traditional function expression
const traditionalFunction = function(x, y) {
  return x + y;
};

// Arrow function
const arrowFunction = (x, y) => x + y;

console.log(traditionalFunction(2, 3)); // Output: 5
console.log(arrowFunction(2, 3));       // Output: 5

In summary, arrow functions provide a concise syntax for writing functions in JavaScript, with lexical this binding and implicit return. They are particularly useful in scenarios where brevity and clarity are important. However, they may not be suitable for all use cases, especially when you need access to this or the arguments object.

Map, Filter, and Reduce

  1. Map: The map() method creates a new array by calling a provided function on every element in the calling array. It does not mutate the original array.

    const numbers = [1, 2, 3, 4, 5];
    
    // Doubling each element in the array
    const doubledNumbers = numbers.map(num => num * 2);
    console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
    
    
    const books = [
      { title: 'Harry Potter', author: 'J.K. Rowling', pages: 336 },
      { title: 'The Lord of the Rings', author: 'J.R.R. Tolkien', pages: 1178 },
      { title: 'To Kill a Mockingbird', author: 'Harper Lee', pages: 281 }
    ];
    
    // Create a new array with book titles
    const bookTitles = books.map(book => book.title);
    console.log(bookTitles); // Output: ['Harry Potter', 'The Lord of the Rings', 'To Kill a Mockingbird']
    

    In this example, the map() method iterates through each element of the numbers array, applies the provided function (which doubles each element), and returns a new array with the modified elements.

  2. Filter: The filter() method creates a new array with all elements that pass the test implemented by the provided function. It also does not mutate the original array.

    const numbers = [1, 2, 3, 4, 5];
    
    // Filtering out odd numbers
    const evenNumbers = numbers.filter(num => num % 2 === 0);
    console.log(evenNumbers); // Output: [2, 4]
    
    
    const students = [
      { name: 'Alice', age: 22 },
      { name: 'Bob', age: 18 },
      { name: 'Charlie', age: 25 },
      { name: 'David', age: 30 }
    ];
    
    // Filter students who are older than 20
    const olderStudents = students.filter(student => student.age > 20);
    console.log(olderStudents); // Output: [{ name: 'Alice', age: 22 }, { name: 'Charlie', age: 25 }, { name: 'David', age: 30 }]
    

    Here, the filter() method iterates through each element of the numbers array, applies the provided function (which checks if the number is even), and returns a new array containing only the elements that pass the test.

  3. Reduce: The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value. It can be used to perform any kind of operation on an array and return a single value.

    const numbers = [1, 2, 3, 4, 5];
    
    // Summing up all elements in the array
    const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
    console.log(sum); // Output: 15
    
    
    const orders = [
      { id: 1, product: 'Laptop', price: 1000 },
      { id: 2, product: 'Smartphone', price: 500 },
      { id: 3, product: 'Tablet', price: 300 }
    ];
    
    // Calculate total revenue from orders
    const totalRevenue = orders.reduce((total, order) => total + order.price, 0);
    console.log(totalRevenue); // Output: 1800
    

    In this example, the reduce() method takes a function with two parameters: an accumulator (which stores the accumulated result) and the current value being processed. It starts with an initial value of 0 (specified as the second argument), then adds each element of the array to the accumulator, returning the final accumulated value.

  4. Combination of Filter, Map & Reduce :

       const transactions = [
         { id: 1, customer: 'Alice', age: 22, amount: 100 },
         { id: 2, customer: 'Bob', age: 25, amount: 200 },
         { id: 3, customer: 'Charlie', age: 30, amount: 150 },
         { id: 4, customer: 'David', age: 28, amount: 300 }
       ];
    
       // Filter transactions made by customers aged 25 and above
       // Map to get the amounts
       // Reduce to sum up the total amount
       const totalAmountSpentByOlderCustomers = transactions
         .filter(transaction => transaction.age >= 25) // Filter
         .map(transaction => transaction.amount) // Map
         .reduce((total, amount) => total + amount, 0); // Reduce
    
       console.log(totalAmountSpentByOlderCustomers); // Output: 650 (200 + 150 + 300)
    

Mutability and Immutability in JavaScript

In JavaScript, mutability and immutability refer to whether an object’s state can be changed after it’s been created.

Mutability:

  1. Objects: Objects in JavaScript are mutable, meaning their properties can be changed after they’re created.

    let obj = { name: 'John', age: 30 };
    obj.age = 31; // Mutable
    
  2. Arrays: Arrays are also mutable in JavaScript.

    let arr = [1, 2, 3];
    arr.push(4); // Mutable
    

Immutability:

  1. Primitives: Primitive data types such as strings, numbers, and booleans are immutable.

    let str = 'Hello';
    str.concat(' World'); // Immutability - 'Hello' remains unchanged
    
  2. Immutable Libraries: Some libraries and patterns in JavaScript promote immutability, such as using libraries like Immutable.js or functions like Object.freeze().

    Example with Object.freeze():

    let obj = Object.freeze({ name: 'John', age: 30 });
    obj.age = 31; // This change will not be allowed in strict mode or throw an error in non-strict mode
    

    Example with Immutable.js:

    const { Map } = require('immutable');
    let map1 = Map({ a: 1, b: 2, c: 3 });
    let map2 = map1.set('b', 50); // Returns a new immutable map with the updated value
    

Benefits of Immutability:

  1. Predictability: Immutable data structures make code easier to reason about because you don’t have to worry about unexpected changes to shared data.
  2. Concurrency: In a multi-threaded environment, immutability helps avoid race conditions and data inconsistency issues.
  3. Performance: Immutable data structures can be optimized for certain operations, leading to better performance in some cases.
  4. Debugging: Immutable data makes debugging easier since you can trace changes more effectively.

Drawbacks of Immutability:

  1. Memory Overhead: Immutable data structures often involve creating new copies, which can lead to increased memory usage.
  2. Performance Trade-offs: While immutability can improve performance in some scenarios, it may degrade performance in others, especially for large data sets or frequent updates.
  3. Learning Curve: Adopting immutable patterns or libraries may require a learning curve for developers accustomed to mutable data structures.

Destructuring in JavaScript

Destructuring in JavaScript is a convenient way of extracting multiple values from data stored in arrays or objects, and assigning them to variables in a concise manner. Here are two examples demonstrating destructuring:

Example 1: Destructuring Arrays

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

// Destructuring assignment
const [first, second, ...rest] = numbers;

// Using the extracted values
console.log(first);  // Output: 1
console.log(second); // Output: 2
console.log(rest);   // Output: [3, 4, 5]

In this example, an array numbers is destructured into variables first, second, and rest. The first two elements of the array are assigned to first and second, while the rest of the elements are captured in the rest variable using the rest syntax (...).

Example 2: Destructuring Objects

// Object destructuring
const person = {
  name: 'John Doe',
  age: 30,
  gender: 'male',
  country: 'USA'
};

// Destructuring assignment
const { name, age, ...details } = person;

// Using the extracted values
console.log(name);    // Output: John Doe
console.log(age);     // Output: 30
console.log(details); // Output: { gender: 'male', country: 'USA' }

Here, an object person is destructured into variables name, age, and details. The name and age properties are assigned to corresponding variables, while the remaining properties are captured in the details object using the rest syntax (...).

In JavaScript, destructuring assignment can be used not only with arrays and objects but also with other iterable data structures like strings and sets. Here are examples demonstrating destructuring with strings and sets:

Example 3: Destructuring Strings:

// String destructuring
const str = 'hello';

// Destructuring assignment
const [firstChar, secondChar, ...remainingChars] = str;

// Using the extracted values
console.log(firstChar);       // Output: h
console.log(secondChar);      // Output: e
console.log(remainingChars);  // Output: ['l', 'l', 'o']

In this example, a string str is destructured into individual characters using array destructuring.

Example 4: Destructuring Sets:

// Set destructuring
const mySet = new Set([1, 2, 3, 4, 5]);

// Destructuring assignment
const [firstElement, secondElement, ...restElements] = mySet;

// Using the extracted values
console.log(firstElement);    // Output: 1
console.log(secondElement);   // Output: 2
console.log(restElements);    // Output: [3, 4, 5]

Here, a Set named mySet is destructured into individual elements using array destructuring. Sets are iterables, so they can be destructured just like arrays.

So, in JavaScript, while arrays and objects are the most commonly used data structures for destructuring, you can also destructure strings, sets, and any other iterable data structures.