Topics in this subject
JavaScript 4 min read Updated 5 Aug 2026

Objects

Creating objects, Property descriptors, Prototypes & the prototype chain 🎯, Inheritance patterns, Key object methods

Creating objects#

const literal = { a: 1, b: 2 };            // 🟢 most common
const made    = Object.create(proto);      // set prototype explicitly
const built   = new Constructor();         // via constructor
// shorthand & computed keys
const k = "id";
const o = { k1: 1, [k]: 2, method() {}, get full() { return "x"; } };

Property descriptors#

Every property has hidden attributes:

const o = {};
Object.defineProperty(o, "x", {
  value: 42,
  writable: false,     // can't reassign
  enumerable: false,   // hidden from for...in / Object.keys / JSON
  configurable: false, // can't delete or redefine
});
Object.getOwnPropertyDescriptor(o, "x");

Descriptors also define accessors (get/set) instead of value/writable. Literals create properties that are writable, enumerable, configurable by default.

Prototypes & the prototype chain 🎯#

ELI12. Every object has a secret link to another object called its prototype. When you ask for a property the object doesn't have, JavaScript follows that link up a chain until it finds it or hits null.

graph TD
    inst["myArr = [1,2]"] -->|"[[Prototype]]"| ap["Array.prototype<br/>(map, filter, push...)"]
    ap -->|"[[Prototype]]"| op["Object.prototype<br/>(toString, hasOwnProperty)"]
    op -->|"[[Prototype]]"| n["null"]
const obj = { a: 1 };
obj.hasOwnProperty("a");            // true — found on Object.prototype
Object.getPrototypeOf(obj) === Object.prototype;  // true
Object.getPrototypeOf(Object.prototype);          // null (top of chain)
  • __proto__ 🔴 is the legacy accessor for the prototype link; use Object.getPrototypeOf/Object.setPrototypeOf.
  • Constructor.prototype is the object that instances get as their prototype.
  • ⚠️ Setting prototypes at runtime (Object.setPrototypeOf) is a big performance hit — set the shape at creation.

Inheritance patterns#

// prototypal
const animal = { speak() { return `${this.name} makes a sound`; } };
const dog = Object.create(animal);
dog.name = "Rex";
dog.speak();   // "Rex makes a sound"

// via classes (sugar over the above) — see Object-Oriented JavaScript

Key object methods#

Method Purpose
Object.keys(o) own enumerable keys (array)
Object.values(o) own enumerable values
Object.entries(o) [key, value] pairs
Object.fromEntries(pairs) inverse of entries
Object.assign(t, ...src) shallow-merge sources into target
Object.create(proto, descs?) new object with given prototype
Object.freeze(o) shallow-immutable (no add/change/delete)
Object.seal(o) no add/delete, but existing writable
Object.preventExtensions(o) no add; existing editable/deletable
Object.hasOwn(o, k) 🟢 own-property check (replaces hasOwnProperty)
Object.getOwnPropertyNames(o) includes non-enumerable string keys
Object.groupBy(items, fn) 🟢 group array items into an object by key
structuredClone(o) 🟢 deep clone (handles Dates, Maps, cycles)
Object.freeze(o);   // shallow!
Object.isFrozen(o); // true

// group by (ES2024)
Object.groupBy([1,2,3,4], n => n % 2 ? "odd" : "even");
// { odd: [1,3], even: [2,4] }

Shallow vs deep clone 🎯#

const shallow1 = { ...obj };            // spread — 1 level deep
const shallow2 = Object.assign({}, obj);
const deepJSON = JSON.parse(JSON.stringify(obj)); // ⚠️ drops functions, undefined,
                                                  // Dates→strings, breaks on cycles
const deep = structuredClone(obj);      // 🟢 correct deep clone, handles cycles/Map/Set/Date

⚠️ Spread and Object.assign copy nested objects by reference — mutating a nested object in the "copy" mutates the original.

Reflect & Proxy (metaprogramming)#

Proxy wraps an object to intercept operations (get/set/has/delete/…). Reflect provides the default behaviours as functions, ideal to call from a trap.

const target = { balance: 100 };
const guarded = new Proxy(target, {
  get(obj, prop, recv) {
    console.log("read", prop);
    return Reflect.get(obj, prop, recv);
  },
  set(obj, prop, value, recv) {
    if (prop === "balance" && value < 0) throw new Error("no overdraft");
    return Reflect.set(obj, prop, value, recv);
  },
});
guarded.balance;        // logs "read balance" → 100
guarded.balance = -5;   // throws

Uses: validation, reactive state (Vue 3's reactivity is built on Proxy), logging, default values, virtual/lazy objects.

Interview Q&A#

Q1. Explain the prototype chain and how property lookup works. Every object has a hidden [[Prototype]] link to another object. On a property read, JS checks the object itself, then walks up the chain (e.g. instance → Array.prototypeObject.prototypenull) until it finds the key or reaches null (yielding undefined).

Q2. Object.freeze vs Object.seal vs preventExtensions? preventExtensions blocks adding new properties (existing ones stay editable/deletable). seal also blocks deletion but existing values remain writable. freeze is the strictest — no add, delete, or change. All three are shallow.

Q3. Three ways to clone an object and each one's pitfalls. Spread { ...obj } / Object.assign({}, obj) are shallow (nested objects shared by reference). JSON.parse(JSON.stringify(obj)) is deep but drops functions/undefined, turns Dates into strings, and throws on cycles. structuredClone(obj) is the correct deep clone (handles cycles, Map/Set/Date).

Q4. What problem does Proxy solve? Give a real use. A Proxy intercepts fundamental operations (get/set/has/delete) so you can add behavior without touching the target, typically delegating defaults via Reflect. Real uses: validation, logging, virtual/lazy objects, and reactive state — Vue 3's reactivity is built on Proxy.