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.

🧰 Real-life analogy

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.

MethodChange existing values?Add new properties?Delete properties?
Object.freeze()NoNoNo
Object.seal()YesNoNo
Neither (normal object)YesYesYes

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 stored
  • writable — whether the value can be changed after being set
  • enumerable — whether the property shows up in loops and Object.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().

💡 Tip

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
⚠ Use with caution

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

⚠ Watch out for
  • Assuming Object.freeze() deeply protects an entire object — it only locks the top level; nested objects remain fully editable.
  • Confusing freeze() (nothing can change) with seal() (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 }) over Object.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 with Object.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)