Object Methods
Beyond dot notation, the built-in Object object gives you a whole toolbox for copying, locking, inspecting, and transforming objects safely.
What Is the Object Object?
Object is a built-in JavaScript tool that isn't itself a piece of data you store — it's a toolbox of static methods, called directly as Object.methodName(...), for working with any object you already have.
If your objects are individual filing folders, the Object toolbox is the office supply cabinet: a locking mechanism to seal a folder shut (freeze), a photocopier to duplicate contents into a new folder (assign), and label-readers that list everything written on the folder's tabs (keys/values/entries). None of these tools live inside the folder itself — they're separate tools you apply to a folder.
Reading Structure: keys(), values(), entries()
const user = { name: "Maya", age: 27 };
Object.keys(user); // ["name", "age"]
Object.values(user); // ["Maya", 27]
Object.entries(user); // [["name","Maya"], ["age",27]]
These were introduced briefly in Objects: The Basics — they're the standard bridge between objects and arrays, letting you use array methods like map/filter on an object's data.
Object.fromEntries(): The Reverse Direction
const pairs = [["name", "Maya"], ["age", 27]];
Object.fromEntries(pairs); // { name: "Maya", age: 27 } — turns pairs BACK into an object
// Handy for transforming an object's values via array methods, then rebuilding it:
const prices = { apple: 1, banana: 2 };
const doubled = Object.fromEntries(
Object.entries(prices).map(([key, value]) => [key, value * 2])
);
// { apple: 2, banana: 4 }
Copying: Object.assign() and Spread
const defaults = { theme: "dark", fontSize: 14 };
const overrides = { fontSize: 18 };
const settings = Object.assign({}, defaults, overrides);
// { theme: "dark", fontSize: 18 } — later sources overwrite earlier ones
// The modern, more common equivalent using spread:
const settings2 = { ...defaults, ...overrides };
// { theme: "dark", fontSize: 18 } — identical result, cleaner syntax
Object.assign(target, ...sources)— copies properties from each source object into the target, left to right, with later sources overwriting matching keys from earlier ones- Passing
{}as the target creates a new object instead of mutating an existing one — a very common pattern for "merge these objects into a new one" - Both
Object.assign()and spread perform a shallow copy — nested objects inside are still shared by reference, not deeply duplicated
const original = { info: { age: 27 } };
const copy = { ...original };
copy.info.age = 99;
original.info.age; // 99 — the nested object was NOT deeply copied, both still point to the same inner object
Locking Objects: freeze() and seal()
const config = { apiUrl: "https://api.example.com" };
Object.freeze(config);
config.apiUrl = "https://hacked.com"; // silently fails (throws in strict mode)
config.newProp = "x"; // also silently fails — can't add either
console.log(config.apiUrl); // still "https://api.example.com"
Object.freeze() completely locks an object: existing properties can't be changed, and no new properties can be added or removed. Object.seal() is a lighter version — existing properties can still be changed, but nothing can be added or removed.
| Method | Change existing values? | Add new properties? | Delete properties? |
|---|---|---|---|
Object.freeze() | No | No | No |
Object.seal() | Yes | No | No |
| Neither (normal object) | Yes | Yes | Yes |
Both are shallow — freezing an object doesn't freeze objects nested inside it, which is a very common surprise.
const settings = Object.freeze({ nested: { locked: false } });
settings.nested.locked = true; // this WORKS — freeze only protects the top level
Checking a Lock: isFrozen() and isSealed()
Object.isFrozen(config); // true
Object.isSealed(config); // true — every frozen object is automatically also sealed
Object.create(): Building From a Prototype
const animalMethods = {
describe() { return `I am a ${this.type}`; }
};
const dog = Object.create(animalMethods);
dog.type = "dog";
dog.describe(); // "I am a dog" — describe() is inherited via the prototype chain
Object.create(proto) builds a brand-new, empty object whose prototype is set directly to whatever you pass in — the manual, low-level way of setting up the prototype chain covered in Prototypes & Prototype Chain. Passing null creates a truly bare object with no inherited methods at all, not even the usual ones like toString().
Fine-Grained Control: defineProperty() and Descriptors
Every property on an object secretly has more information attached to it than just its value — called a property descriptor. Normally you never see this, but Object.defineProperty() lets you control it directly.
const user = {};
Object.defineProperty(user, "name", {
value: "Maya",
writable: false, // can't be reassigned
enumerable: true, // shows up in for...in / Object.keys()
configurable: false // can't be deleted or redefined
});
user.name = "Sam"; // silently fails — writable is false
user.name; // still "Maya"
value— the actual data storedwritable— whether the value can be changed after being setenumerable— whether the property shows up in loops andObject.keys()/entries()configurable— whether the property can be deleted, or have its descriptor changed again later
By default, properties you create the normal way (obj.x = 5) get all three flags set to true automatically. Object.freeze() is essentially a shortcut that sets writable and configurable to false on every property at once.
Object.getOwnPropertyDescriptor(user, "name");
// { value: "Maya", writable: false, enumerable: true, configurable: false }
Checking Own vs. Inherited Properties
Object.hasOwn(user, "name"); // true — modern, recommended way (2022)
user.hasOwnProperty("name"); // true — the older, still-common way
Object.getOwnPropertyNames(user); // ["name"] — includes non-enumerable properties too, unlike Object.keys()
Object.hasOwn() is the newer, safer version of hasOwnProperty() — it works correctly even on objects created with Object.create(null), which don't have hasOwnProperty available on them at all.
Deep Copying: structuredClone()
const original = { name: "Maya", scores: [90, 85], address: { city: "Lagos" } };
// Shallow copies — nested objects are STILL shared:
const shallow = { ...original };
shallow.address.city = "Abuja";
original.address.city; // "Abuja" — oops, both changed
// Deep copy — every level is fully independent:
const deep = structuredClone(original);
deep.address.city = "Abuja";
original.address.city; // "Lagos" — original is safe
deep.scores.push(100);
original.scores; // [90, 85] — also untouched
structuredClone() is a modern browser/Node built-in that creates a completely independent copy of an object, no matter how many layers deep it goes. It handles nested objects, arrays, dates, maps, sets, and more. Use it any time you need a true copy — not the shallow imitation you get from spread or Object.assign().
One thing structuredClone() cannot copy: functions. If your object has methods, those get dropped. For plain data objects (no functions), it's the cleanest deep-copy tool available.
Precise Equality: Object.is()
// The two edge cases where === gives a surprising answer:
NaN === NaN; // false — NaN is never equal to itself in regular JS
+0 === -0; // true — === treats positive and negative zero as the same
// Object.is() fixes both:
Object.is(NaN, NaN); // true — correctly says NaN equals itself
Object.is(+0, -0); // false — correctly tells them apart
// Everything else works exactly like ===:
Object.is(1, 1); // true
Object.is("hi", "hi"); // true
Object.is({}, {}); // false — still compares references, not contents
Object.is() is like === but with two edge-case bugs fixed. You will rarely need it in everyday code, but it comes up in utility library code, memoization helpers, and any situation where you need to correctly detect NaN or distinguish +0 from -0.
Defining Multiple Properties at Once: Object.defineProperties()
const product = {};
Object.defineProperties(product, {
id: {
value: 42,
writable: false,
enumerable: true,
configurable: false
},
name: {
value: "Keyboard",
writable: true,
enumerable: true,
configurable: true
}
});
product.id; // 42
product.name; // "Keyboard"
product.id = 99; // silently fails — id is non-writable
This is just Object.defineProperty() called once for every entry in the descriptor map you pass in. Use it when you need to configure several properties with precise descriptors all at once, instead of writing separate defineProperty() calls for each one.
Reading All Descriptors at Once: Object.getOwnPropertyDescriptors()
const user = { name: "Maya", age: 27 };
Object.getOwnPropertyDescriptors(user);
/*
{
name: { value: "Maya", writable: true, enumerable: true, configurable: true },
age: { value: 27, writable: true, enumerable: true, configurable: true }
}
*/
// The practical use: copying an object INCLUDING its property descriptors
const frozen = Object.freeze({ score: 100 });
const shallowCopy = { ...frozen }; // score is now writable again — descriptors not copied
const fullCopy = Object.create(
Object.getPrototypeOf(frozen),
Object.getOwnPropertyDescriptors(frozen) // preserves all the writable/configurable flags
);
Object.isFrozen(fullCopy); // true — flags were preserved in the copy
The spread operator and Object.assign() copy property values only — they silently drop all descriptor information like writable: false or configurable: false. getOwnPropertyDescriptors() gives you everything, which you can then feed into Object.create() for a truly faithful copy of an object's full property setup.
Changing a Prototype After Creation: Object.setPrototypeOf()
const flyer = {
fly() { return "I can fly!"; }
};
const bird = { name: "Robin" };
// bird currently has no fly() — give it one by swapping its prototype:
Object.setPrototypeOf(bird, flyer);
bird.fly(); // "I can fly!" — inherited from flyer via the prototype chain
Object.getPrototypeOf(bird) === flyer; // true
Changing an object's prototype after it's been created is very slow in JavaScript engines — it forces the engine to throw away all its internal optimizations for that object. It's almost always better to set the prototype at creation time with Object.create(proto) instead. setPrototypeOf() exists for specific edge cases, not general use.
Common Mistakes
- Assuming
Object.freeze()deeply protects an entire object — it only locks the top level; nested objects remain fully editable. - Confusing
freeze()(nothing can change) withseal()(existing values can still change, but nothing can be added/removed) — they solve different problems. - Using
Object.assign()or spread expecting a deep copy — both only copy one level deep; nested objects/arrays are still shared by reference.
Best Practices
- Use spread (
{ ...obj }) overObject.assign()for everyday copying and merging — it reads more clearly for most cases. - Reach for
Object.freeze()on configuration objects or constants you want to guarantee stay unchanged through a program's lifetime. - Use
structuredClone(obj)(a separate built-in browser/Node function) when you truly need a deep copy, rather than reaching for tricks withObject.assign().
Performance Notes
Object.keys()/values()/entries() create a brand-new array each time they're called, which has a small cost if called repeatedly on the same object inside a hot loop — store the result in a variable if you need it more than once. Frozen objects can actually help engines optimize certain code paths, since the engine knows the shape will never change.
Browser Compatibility
Object.keys(), freeze(), seal(), create(), and defineProperty() have been supported since ES5 (2009). Object.entries()/values() arrived in ES2017, and Object.fromEntries()/Object.hasOwn() are newer still (2019 and 2022 respectively) but supported in all current browsers.
Interview Questions
What's the difference between Object.freeze() and Object.seal()?
freeze() prevents any change at all — no new properties, no deletions, and no changes to existing values. seal() is less strict: it still blocks adding or removing properties, but existing property values can still be reassigned.
Does Object.assign() or the spread operator create a deep copy?
No, both only perform a shallow copy — top-level properties are copied independently, but any nested objects or arrays inside are still shared by reference between the original and the copy.
✏️ Exercise
Create an object settings, freeze it with Object.freeze(), then attempt to change one of its properties and log the object afterward to confirm nothing changed.
🧠 Quiz
1. Which Object method converts an object into an array of [key, value] pairs?
Object.entries().
2. What does Object.fromEntries() do?
It converts an array of [key, value] pairs back into a real object — the reverse operation of Object.entries().
🚀 Mini Challenge
Write a function deepFreeze(obj) that freezes an object and recursively freezes every nested object inside it, so the whole structure — not just the top level — becomes fully immutable.
Summary
Object.keys()/values()/entries()read an object's structure;fromEntries()rebuilds one.Object.assign()and spread copy/merge objects shallowly;freeze()/seal()lock them from changes at different strictness levels.Object.defineProperty()and property descriptors give fine-grained control over whether a property can be changed, listed, or deleted.
Related Topics
MDN reference: developer.mozilla.org → JavaScript → Object (placeholder — add live link)