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

Part 10 — 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 Part 19

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 questions (Part 10):

  1. Explain the prototype chain and how property lookup works.
  2. Object.freeze vs Object.seal vs preventExtensions?
  3. Three ways to clone an object and each one's pitfalls.
  4. What problem does Proxy solve? Give a real use.