Extending Objects in JavaScript
Introduction
Real programs merge defaults, clone objects, and extend prototypes or instances. This chapter covers object spread, Object.assign, shallow vs deep copies, and prototype extension at a beginner-safe level. These patterns appear in configuration, state updates, and utility libraries.
Prerequisites
Shallow Merge with Spread
// Later sources override earlier keys
const defaults = { retries: 1, timeout: 3000 };
const overrides = { timeout: 5000, debug: true };
const config = { ...defaults, ...overrides };
console.log(config);Only the first level is copied—nested objects are shared by reference.
Object.assign
Similar merge behavior:
// Object.assign mutates target and returns it
const target = { a: 1 };
const source = { b: 2, a: 9 };
Object.assign(target, source);
console.log(target);Prefer spread for immutable-style updates: const next = { ...prev, ...patch }.
Shallow Clone
// Spread clone (shallow)
const original = { name: "Ada", tags: ["js"] };
const copy = { ...original };
copy.name = "Lin";
copy.tags.push("python");
console.log(original.tags);
console.log(copy.tags);Both tags arrays point to the same array—mutating nested data affects both.
Deep Clone with structuredClone
// Deep clone when supported
const original = { user: { name: "Ada", scores: [90, 88] } };
const deep = structuredClone(original);
deep.user.scores.push(100);
console.log(original.user.scores.length);
console.log(deep.user.scores.length);Warning
structuredClone cannot clone some types (functions, certain DOM nodes). For app config JSON-like data it works well.
Freezing and Sealing (Immutability Helpers)
// Prevent adds/changes/deletes
const constants = Object.freeze({ pi: 3.14159, e: 2.718 });
// constants.pi = 3; // fails silently or throws in strict modeObject.seal and Object.preventExtensions are middle grounds—advanced API design topic.
Prototype Extension (Preview)
Objects inherit from Object.prototype by default. You can add methods to constructors’ prototypes—prefer class methods in modern code:
// Avoid modifying built-in prototypes in libraries
// Educational example on a custom constructor
function Team(name) {
this.name = name;
this.members = [];
}
Team.prototype.add = function (member) {
this.members.push(member);
};
const t = new Team("Core");
t.add("Ada");
console.log(t.members);Mini Example: Merge Feature Flags
// Feature flags by environment
const globalFlags = {
newCheckout: false,
betaBanner: true,
analytics: { enabled: true, sampleRate: 0.1 },
};
const devFlags = {
newCheckout: true,
analytics: { enabled: true, sampleRate: 1 },
};
const flags = {
...globalFlags,
...devFlags,
analytics: { ...globalFlags.analytics, ...devFlags.analytics },
};
console.log(flags);FAQ
Spread vs Object.assign?
Both shallow-merge. Spread is concise for new objects; assign mutates a target.
How do I merge arrays of objects?
Use .map, loops, or utilities—patterns depend on whether you merge by id or index.
Is immutability required?
Not by the language, but immutable updates reduce bugs in UI state—especially with React-style flows.
What comes next?
JSON serialization, then arrays.