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; useObject.getPrototypeOf/Object.setPrototypeOf.Constructor.prototypeis 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):
- Explain the prototype chain and how property lookup works.
Object.freezevsObject.sealvspreventExtensions?- Three ways to clone an object and each one's pitfalls.
- What problem does
Proxysolve? Give a real use.