Arrays , Objects and Functions
Arrays in JavaScript
Arrays in JavaScript are like ordered lists that:
- Start counting from 0 (e.g., first item is at position 0)
- Can grow or shrink in size
- Can store any type of data (numbers, strings, objects, even other arrays)
- Can be modified after creation
// Basic array creation and usageconst numbers = [1, 2, 3, 4, 5];const mixed = ['text', 42, true, { key: 'value' }, [1, 2]];
Array Copy Behaviors
1. Reference Copy
The simplest form of copying - creates a new reference to the same array.
const original = [1, 2, {x: 3}];const reference = original; // Same array in memory
reference.push(4);console.log(original); // [1, 2, {x: 3}, 4] (affected)console.log(reference); // [1, 2, {x: 3}, 4] (affected)
2. Shallow Copy
Creates a new array but maintains references to nested objects.
Shallow Copy Visualization ═══════════════════════════════════
Original Array Copied Array┌──────────────┐ ┌──────────────┐│ arr1 │ │ arr2 │├──────────────┤ ├──────────────┤│ [0]: 1 │ │ [0]: 1 ││ [1]: 2 │ │ [1]: 2 ││ [2]: ─────┐ │ │ [2]: ─────┐ │└───────────┼──┘ └───────────┼──┘ │ │ │ Heap Memory │ │ ┌────────────┐ │ └─→│ {x: 10} │←────┘ └────────────┘
// Shallow copy methodsconst original = [1, 2, {x: 3}];const shallowCopy1 = [...original]; // Spread operatorconst shallowCopy2 = Array.from(original); // Array.from()const shallowCopy3 = original.slice(); // slice()
// Demonstrating shared referencesshallowCopy1[2].x = 100;console.log(original[2].x); // 100 (nested object affected)
3. Deep Copy
Creates a completely independent copy with new references for all nested items.
Deep Copy Visualization ═══════════════════════════════════
Original Array Deep Copied Array┌──────────────┐ ┌──────────────┐│ arr1 │ │ arr2 │├──────────────┤ ├──────────────┤│ [0]: 1 │ │ [0]: 1 ││ [1]: 2 │ │ [1]: 2 ││ [2]: ─────┐ │ │ [2]: ─────┐ │└───────────┼──┘ └───────────┼──┘ │ │ │ Heap Memory │ │ ┌────────────┐ │ ┌────────────┐ └─→│ {x: 10} │ └─→│ {x: 10} │ └────────────┘ └────────────┘ (Object #1) (Object #2)
// Modern deep copy methodsconst original = [1, 2, {x: 3}];
// Method 1: structuredClone (recommended)const deepCopy1 = structuredClone(original);
// Method 2: JSON parse/stringify (with limitations)const deepCopy2 = JSON.parse(JSON.stringify(original));
// Demonstrating independencedeepCopy1[2].x = 200;console.log(original[2].x); // Still 3 (unaffected)
Example
const users = [ { id: 1, name: 'Alice', settings: { theme: 'dark' } }, { id: 2, name: 'Bob', settings: { theme: 'light' } }];
// Shallow copy behaviorconst shallowUsers = [...users];shallowUsers[0].settings.theme = 'light';console.log(users[0].settings.theme); // 'light' (affected)
// Deep copy behaviorconst deepUsers = structuredClone(users);deepUsers[0].settings.theme = 'dark';console.log(users[0].settings.theme); // 'light' (unaffected)
Array Methods
Common array methods can be categorized by whether they modify the original array:
Non-mutating Methods (Return new array)
const arr = [1, 2, 3, 4, 5];arr.map(item => item * 2); // Returns [2, 4, 6, 8, 10], arr unchangedarr.filter(item => item % 2 === 0); // Returns [2, 4], arr unchangedarr.reduce((acc, item) => acc + item, 0); // Returns 15, arr unchangedarr.slice(1, 4); // Returns [2, 3, 4], arr unchangedarr.forEach(item => console.log(item)); // Returns undefined, arr unchangedarr.join('-'); // Returns '1-2-3-4-5', arr unchanged
Mutating Methods (Modify original array)
const arr = [1, 2, 3, 4, 5];arr.splice(1, 2); // Returns [2, 3], arr is now [1, 4, 5]arr.push(6); // Returns new length 4, arr is now [1, 4, 5, 6]arr.pop(); // Returns 6, arr is now [1, 4, 5]arr.forEach(item => { // Returns undefined, can mutate arr if callback modifies it console.log(item);});arr.sort((a, b) => a - b); // Returns sorted array, arr is now [1, 4, 5, 6].// Note: sort() method modifies the original array.
Chaining Methods
Higher-order methods can be chained together for complex operations.
const products = [ { name: 'Laptop', price: 1000, inStock: true }, { name: 'Phone', price: 500, inStock: false }, { name: 'Tablet', price: 300, inStock: true }];
const totalInStock = products .filter(product => product.inStock) .map(product => product.price) .reduce((acc, price) => acc + price, 0);
console.log(totalInStock); // 1300
Array Loops
- Array loops are used to iterate over the elements of an array.
const numbers = [1, 2, 3, 4, 5];for (let i = 0; i < numbers.length; i++) { console.log(numbers[i]);}
forEach()
The forEach()
method executes a provided function once for each array element.
// Basic syntaxarray.forEach(function(element, index, array) { // Your code here});
// Simple exampleconst numbers = [1, 2, 3];numbers.forEach(num => console.log(num)); // Logs: 1, 2, 3
// With indexnumbers.forEach((num, index) => { console.log(`Index ${index}: ${num}`);}); // Logs: "Index 0: 1", "Index 1: 2", "Index 2: 3"
// With arraynumbers.forEach((num, index, array) => { console.log(array);}); // Logs: [1, 2, 3]
Key Features:
- Always returns
undefined
- Cannot break or return from the loop
- Skips empty array elements
- Modifies the original array if the callback function does so
Common Use Cases:
// 1. Modifying objects in an arrayconst users = [ { name: 'Alice', active: false }, { name: 'Bob', active: false }];users.forEach(user => user.active = true);
// 2. Side effects (like DOM updates)const items = ['apple', 'banana', 'orange'];items.forEach(item => { const li = document.createElement('li'); li.textContent = item; list.appendChild(li);});
// 3. Accumulating valueslet sum = 0;[1, 2, 3].forEach(num => sum += num);
Objects
- Objects are collections of key-value pairs
- Keys are strings (or symbols), values can be any type of data
- Objects are mutable
- Objects are unordered
const user = { name: 'Alice', age: 25, isAdmin: true, "color": red};
console.log(user.name); // Aliceconsole.log(user['age']); // 25console.log(user["color"]); // red
Object Methods
const obj1 = { name: "Alice", age: 25};
const obj2 = { name1: "Bob", age1: 30};
const obj3 = {...obj1, ...obj2}; // Spread operator
console.log(obj3.name); // Aliceconsole.log(obj3.age1); // 30console.log(obj3); // { name: 'Alice', age: 25, name1: 'Bob', age1: 30 }
- To learn more about the object methods, visit MDN.
Map and Set Collections
Map
- A collection that lets you store key-value pairs where keys can be of any type (objects, functions, primitives).
- Map is not iterable for in loop.
// Creating and using Mapsconst userRoles = new Map();userRoles.set('john', 'admin'); // Add entryuserRoles.get('john'); // Get value: 'admin'userRoles.has('john'); // Check existence: trueuserRoles.delete('john'); // Remove entryuserRoles.size; // Get size
// Iteratingfor (const [user, role] of userRoles) { console.log(`${user} is ${role}`);}
Common Use Cases:
- Caching/memoization results
// Example: Simple cacheconst cache = new Map();function expensiveOperation(input) { if (cache.has(input)) return cache.get(input); const result = /* complex calculation */; cache.set(input, result); return result;}
Set
- A collection of unique values where duplicates are automatically removed.
// Creating and using Setsconst uniqueTags = new Set(['js', 'python', 'js']); // Duplicates removeduniqueTags.add('java'); // Add valueuniqueTags.has('python'); // Check existence: trueuniqueTags.delete('js'); // Remove valueuniqueTags.size; // Get size
// Quick array deduplicationconst array = [1, 2, 2, 3, 3];const unique = [...new Set(array)]; // [1, 2, 3]
Common Use Cases:
- Removing duplicates from arrays
- Tracking unique visitors/events
- Managing selected items in UI
// Example: Tracking unique usersconst activeUsers = new Set();function trackUserActivity(userId) { activeUsers.add(userId); console.log(`Active users: ${activeUsers.size}`);}
Destructuring
Destructuring allows you to unpack values from arrays or properties from objects into distinct variables. This syntax makes it easier to work with complex data structures.
Object Destructuring
// Basic destructuringconst person = { name: 'Alice', age: 25, city: 'London' };const { name, age } = person;console.log(name); // 'Alice'console.log(age); // 25
// Destructuring with different variable namesconst { name: firstName, age: yearsOld } = person;console.log(firstName); // 'Alice'console.log(yearsOld); // 25
// Default valuesconst { country = 'Unknown' } = person;console.log(country); // 'Unknown' (since it wasn't in person object)
Array Destructuring
// Basic array destructuringconst colors = ['red', 'green', 'blue'];const [first, second] = colors;console.log(first); // 'red'console.log(second); // 'green'
// Skip elements using commasconst [,, third] = colors;console.log(third); // 'blue'
// Rest patternconst [primary, ...rest] = colors;console.log(primary); // 'red'console.log(rest); // ['green', 'blue']
Nested Destructuring
const data = { user: { id: 1, details: { firstName: 'Alice', lastName: 'Smith' } }, scores: [10, 20, 30]};
// Destructuring nested objects and arraysconst { user: { details: { firstName, lastName }, id }, scores: [firstScore, ...otherScores]} = data;
console.log(firstName); // 'Alice'console.log(id); // 1console.log(firstScore); // 10console.log(otherScores); // [20, 30]
JSON
// JSON is a string representation of a JavaScript object.
{ "name": "Alice", "age": 25, "data1":{ "name1": "Bob", "age1": 30, "data2":{ "name2": "Charlie", "age2": 35, "data3":{ "name3": "David", "age3": 40 } } }}
// JSON.parse() is used to parse a JSON string and convert it into a JavaScript object.// JSON.stringify() is used to convert a JavaScript object into a JSON string.
Functions
- Functions are blocks of code that perform a specific task.
- Functions can take input parameters and return output values.
- Functions can be used to organize code and make it more modular and reusable.
function add(a, b) { return a + b;}
console.log(add(1, 2)); // 3
Functions with Objects and Arrays
Functions can work with complex data types like objects and arrays. Here are some common patterns:
// Object as parameterfunction processUser(user) { return `${user.firstName} ${user.lastName} is ${user.age} years old`;}
const user = { firstName: 'John', lastName: 'Doe', age: 30};console.log(processUser(user)); // "John Doe is 30 years old"
// Array as parameterfunction calculateAverage(numbers) { const sum = numbers.reduce((acc, curr) => acc + curr, 0); return sum / numbers.length;}
const scores = [85, 92, 78, 95, 88];console.log(calculateAverage(scores)); // 87.6
// Returning an objectfunction createPerson(name, age) { return { name, age, greet() { return `Hello, I'm ${this.name}`; } };}
// Returning an arrayfunction splitAndSort(text) { return text.split(' ').sort();}
console.log(splitAndSort('apple banana cherry')); // ['apple', 'banana', 'cherry']
Variable Scope
JavaScript has several types of scope:
- Global Scope: Variables accessible throughout the entire program
- Function Scope: Variables only accessible within their containing function
- Block Scope: Variables (using
let
/const
) only accessible within their containing block
// Global scopeconst globalVar = 'I am global';
function exampleFunction() { // Function scope const functionVar = 'I am function-scoped';
if (true) { // Block scope const blockVar = 'I am block-scoped'; console.log(blockVar); // Works console.log(functionVar); // Works console.log(globalVar); // Works }
console.log(functionVar); // Works console.log(blockVar); // ReferenceError: blockVar is not defined}
console.log(globalVar); // Worksconsole.log(functionVar); // ReferenceError: functionVar is not defined
Hoisting: A JavaScript Legacy Challenge
Hoisting is a JavaScript behavior where variable and function declarations are moved to the top of their scope during code execution. While it’s considered a bad practice in modern JavaScript, understanding it is crucial for maintaining legacy code.
// What developers writeconsole.log(myVar); // undefined (doesn't crash!)var myVar = "Hello";
// What JavaScript actually seesvar myVar; // Declaration is "hoisted" upconsole.log(myVar); // undefinedmyVar = "Hello"; // Assignment stays here
Why Hoisting is Problematic
- Confusing Code Flow:
// Bad practice (but works due to hoisting)calculateTotal(100); // Works! But why?
function calculateTotal(amount) { console.log(amount * tax); // undefined × 100 = NaN var tax = 0.2; // Declaration hoisted, but not the value}
// Better practiceconst tax = 0.2;function calculateTotal(amount) { console.log(amount * tax); // Clear and predictable}calculateTotal(100);
- Function Declaration vs Expression:
// Function declarations are fully hoisteddoSomething(); // Works!function doSomething() { console.log("I'm hoisted!");}
// Function expressions are notdoSomethingElse(); // Error!const doSomethingElse = () => { console.log("I'm not hoisted!");};
Legacy Code Examples
// Common in legacy code - relying on hoistingfunction oldCodebase() { for(var i = 0; i < items.length; i++) { // var is hoisted to function scope } console.log(i); // Still accessible! 😱
if(true) { var secret = "123"; // Hoisted to function scope } console.log(secret); // Accessible outside if block! 😱}
// Modern approachfunction modernCode() { for(let i = 0; i < items.length; i++) { // let has block scope } console.log(i); // ReferenceError ✅
if(true) { const secret = "123"; // Block scoped } console.log(secret); // ReferenceError ✅}
Arrow Functions vs Regular Functions
Arrow functions and regular functions have several key differences in syntax and behavior:
// Regular Functionfunction regularFunction(a, b) { return a + b;}
// Arrow Functionconst arrowFunction = (a, b) => a + b;
Key Differences
this
Binding:
const obj = { name: 'Example', // Regular function regularMethod: function() { setTimeout(function() { console.log(this.name); // undefined }, 100); }, // Arrow function arrowMethod: function() { setTimeout(() => { console.log(this.name); // 'Example' }, 100); }};
obj.regularMethod(); // undefinedobj.arrowMethod(); // Example
- Constructor Usage:
// Regular functions can be constructorsfunction Car(make) { this.make = make;}const myCar = new Car('Toyota'); // Works
// Arrow functions cannot be constructorsconst CarArrow = (make) => { this.make = make;};const myCarArrow = new CarArrow('Toyota'); // TypeError
- Arguments Object:
// Regular function has arguments objectfunction regular() { console.log(arguments); // [1, 2, 3]}
// Arrow function doesn't have argumentsconst arrow = () => { console.log(arguments); // ReferenceError};
regular(1, 2, 3); // [1, 2, 3]arrow(1, 2, 3); // ReferenceError
- Implicit Return:
// Regular function needs explicit returnfunction addRegular(a, b) { return a + b;}
// Arrow function with implicit returnconst addArrow = (a, b) => a + b;
// Arrow function with object implicit returnconst createObj = (name) => ({ name });
IIFE (Immediately Invoked Function Expression)
- IIFE is a function that is defined and executed immediately.
- It is used to create a new execution context and avoid polluting the global scope.
(function() { console.log('IIFE executed!');})(); // IIFE executed!// Parentheses are required to invoke the IIFE.// Semicolons are required after IIFE to separate it from the next statement.
// Arrow functions can also be used as IIFE(() => { console.log('Arrow IIFE executed!');})(); // Arrow IIFE executed!