Skip to main content

1.5 Array & Object Methods & Immutability

← Back to Chapter 1: JavaScript Fundamentals

In modern web development and React architecture, user interfaces are treated as a pure projection of application state (UI = f(state)). Mastering higher-order array methods, object transformations, shallow vs deep copying, and immutable state updates is essential for writing bug-free, high-performance applications.


1. Higher-Order Array Methods​

A higher-order function is a function that accepts other functions as arguments or returns a function.

A. map(): Transforming Elements​

Returns a brand new array by applying a transform callback to every element:

const numbers = [1, 2, 3, 4];
const doubled = numbers.map(num => num * 2); // [2, 4, 6, 8]

B. filter(): Selecting Subsets​

Returns a brand new array containing only elements that satisfy the predicate check:

const tasks = [
{ id: 1, title: "Learn React", completed: true },
{ id: 2, title: "Master Closures", completed: false }
];

const pendingTasks = tasks.filter(task => !task.completed);

C. reduce(): Aggregating to Any Structure​

Accumulates array items into a single final result (number, string, object, array, or lookup dictionary):

const cart = [
{ item: "Keyboard", price: 100, category: "Hardware" },
{ item: "Mouse", price: 50, category: "Hardware" },
{ item: "Course", price: 30, category: "Education" }
];

// Calculate total cart value
const totalPrice = cart.reduce((acc, curr) => acc + curr.price, 0); // 180

// Group items by category into a lookup dictionary
const grouped = cart.reduce((acc, curr) => {
const cat = curr.category;
acc[cat] = acc[cat] || [];
acc[cat].push(curr.item);
return acc;
}, {});
// { Hardware: ['Keyboard', 'Mouse'], Education: ['Course'] }

D. forEach() vs map()​

forEach() executes a callback for every element for side-effects only and always returns undefined. map() returns a new transformed array.

Featuremap()forEach()
Return ValueReturns a new arrayAlways returns undefined
Chainable?Yes (.map().filter())No
Primary Use CasePure data transformation (e.g. rendering JSX lists)Side effects (logging, analytics, mutating external targets)
Usage in React JSXStandard ({items.map(item => <Card key={item.id} />)})Cannot be used inside JSX expressions
const users = ["Alice", "Bob"];

// forEach: for side effects
users.forEach(user => console.log(`Notifying ${user}`)); // returns undefined

// map: creates a new array of objects
const userObjects = users.map(name => ({ name, role: "Member" }));

E. Search & Verification Methods​

  • find(predicate): Returns the first element that satisfies the condition, or undefined.
    const user = users.find(u => u.id === 42);
  • findIndex(predicate): Returns the index of the first match, or -1.
  • some(predicate): Returns true if at least one element passes the check.
    const hasAdmin = users.some(u => u.role === "admin");
  • every(predicate): Returns true if all elements pass the check.
    const allActive = users.every(u => u.isActive);

2. Object Methods​

JavaScript provides powerful built-in static methods on Object for transforming and inspecting objects.

A. Object.keys(), Object.values(), and Object.entries()​

const stats = { speed: 95, health: 120, attack: 85 };

console.log(Object.keys(stats)); // ['speed', 'health', 'attack']
console.log(Object.values(stats)); // [95, 120, 85]
console.log(Object.entries(stats));
// [['speed', 95], ['health', 120], ['attack', 85]]

B. Object.fromEntries(): Rebuilding Objects​

Converts key-value pairs back into an object, making it easy to filter or map over object properties:

// Filter an object to retain only stats with value >= 90
const eliteStats = Object.fromEntries(
Object.entries(stats).filter(([key, value]) => value >= 90)
);
console.log(eliteStats); // { speed: 95, health: 120 }

C. Object.assign()​

Copies all enumerable own properties from one or more source objects to a target object:

const target = { a: 1 };
const source = { b: 2, c: 3 };
const merged = Object.assign({}, target, source); // { a: 1, b: 2, c: 3 }

D. Object.freeze() vs Object.seal()​

  • Object.freeze(obj): Completely freezes an object (shallow). Properties cannot be added, removed, or modified.
  • Object.seal(obj): Prevents adding or removing properties, but existing writable properties can still be modified.

3. Shallow Copy vs Deep Copy​

Because objects and arrays in JavaScript are reference types stored on the heap, understanding how copies work is crucial to preventing unintended mutations.

graph TD
subgraph Shallow Copy [Spread / Object.assign]
O1[Original Object] --> P1[Primitive Value: name]
O1 --> N1[Nested Object: address 0xAA01]
C1[Copied Object] --> P2[Primitive Value: name (independent)]
C1 --> N1
end

subgraph Deep Copy [structuredClone]
O2[Original Object] --> P3[Primitive Value: name]
O2 --> N2[Nested Object: address 0xBB01]
C2[Deep Cloned Object] --> P4[Primitive Value: name]
C2 --> N3[Cloned Nested Object: address 0xCC02 (independent)]
end

The Shallow Copy Trap​

Spread ({ ...obj }), Object.assign({}, obj), and [...arr] only clone the first layer of properties:

const original = {
name: "Sarwvidya",
settings: { theme: "dark" }
};

const shallowCopy = { ...original };
shallowCopy.name = "Alex"; // Safe: primitive string is independent
shallowCopy.settings.theme = "light"; // ❌ BUG: original.settings.theme is also "light"!

console.log(original.settings.theme); // "light" (Mutated unexpectedly!)

Deep Copy Solutions:​

  1. structuredClone() (Modern Standard): Built into modern browsers and Node.js (v17+). Handles circular references, Dates, Sets, Maps, and TypedArrays:

    const deepCopy = structuredClone(original);
    deepCopy.settings.theme = "sepia";
    console.log(original.settings.theme); // "dark" (Untouched!)
  2. JSON.parse(JSON.stringify(obj)) (Legacy Workaround): Simple but has severe limitations: loses functions, undefined, Symbol, Infinity, NaN, and converts Date objects into ISO strings.

  3. lodash.cloneDeep: Common utility library function for older enterprise environments.


4. Immutable Operations in React​

In React, state must NEVER be mutated directly.

Why Immutability is Mandatory​

React components re-render when state changes. To detect if state changed, React performs a blazing-fast shallow reference equality check using Object.is(oldState, newState):

// If you mutate state directly:
state.todos.push(newTodo);
setState(state);
// React checks: Object.is(state, state) -> true!
// Result: React assumes NOTHING changed and skips re-rendering!

By creating a new object or array with an updated reference, React instantly recognizes that state has changed and triggers a DOM update.

The React Immutable Toolkit​

TaskMutating (Avoid in React)Immutable Best Practice
Add item to arrayarr.push(item)[...arr, item] or [item, ...arr]
Remove item from arrayarr.splice(index, 1)arr.filter(item => item.id !== targetId)
Update item in arrayarr[index] = updatedarr.map(item => item.id === targetId ? { ...item, ...updates } : item)
Sort arrayarr.sort()[...arr].sort() or arr.toSorted()
Reverse arrayarr.reverse()[...arr].reverse() or arr.toReversed()
Update object propertyobj.prop = value{ ...obj, prop: value }
Update nested objectobj.nested.prop = value{ ...obj, nested: { ...obj.nested, prop: value } }
// Complex Nested State Update Example
const [userProfile, setUserProfile] = useState({
id: 101,
name: "Sarwvidya",
preferences: {
notifications: {
email: true,
push: false
}
}
});

// Update push notifications immutably:
setUserProfile(prev => ({
...prev,
preferences: {
...prev.preferences,
notifications: {
...prev.preferences.notifications,
push: true
}
}
}));

5. Summary Checklist​

  • Use map() for transforming data into JSX components; remember to provide a unique key.
  • Use filter() and reduce() for declarative filtering, aggregation, and data reshaping.
  • Know the distinction between forEach() (side effects, returns undefined) and map() (pure transform, returns new array).
  • Beware of shallow copies ({ ...obj }): nested references are shared. Use structuredClone() for full deep cloning.
  • Always update React state immutably so that shallow reference checks (Object.is) trigger re-renders properly.