Objects, Prototypes, Classes, Collections, and Errors
Prerequisites: functions and this binding, closure semantics, and array basics. This chapter condenses seven topics; read it as a working model plus gotchas, not a first exposure.
Objects and Property Mechanics
Property access is descriptor-driven: each property has value, writable, enumerable, and configurable. Object.defineProperty controls them; Object.freeze makes them all non-writable and non-configurable (shallow). Assignment through setters bypasses writable, and non-strict writes to frozen objects fail silently — a classic source of "it just didn't change" bugs.
const obj = Object.freeze({ a: 1, nested: { b: 2 } });
obj.nested.b = 99; // frozen is shallow: this works
console.log(obj.nested.b); // 99Also note: in checks the whole chain; Object.keys returns own enumerable string keys only; Object.hasOwn is the safe replacement for hasOwnProperty.
Arrays and Iteration Methods
Iteration methods come in three flavors relevant to decision-making:
| Method | Returns | Mutates | Use when |
|---|---|---|---|
| `map`, `filter`, `slice` | new array | no | building pipelines |
| `sort`, `splice`, `reverse` | same array | yes | in-place editing |
| `reduce` | anything | no | folding to a value |
Gotchas: sort is lexicographic by default — [10, 2].sort() is [10, 2]; pass a comparator. Sparse arrays ([,,]) skip holes in forEach but not in map, which preserves them.
Destructuring and Spread
Destructuring reads a pattern; spread spreads iterables (arrays, strings) or own enumerable props (objects). Defaults apply only to undefined, not null or 0 — a frequent source of confusion in API code.
const { a = 1, b: renamed = 2, ...rest } = { a: null, b: 3, c: 4 };
console.log(a, renamed, rest); // null 3 { c: 4 }Spread is shallow; [...arr].sort() works because sort only mutates one level, but nested structures share references.
Prototype Chain
class is syntax over the prototype chain: methods live on Constructor.prototype; property lookup walks the chain; super resolves statically against the home object. Compose behavior via Object.create and factory functions when classes feel heavy — but note instanceof still walks the chain, so it works either way.
Map, Set, and Built-in Collections
Map preserves insertion order, allows any key type, and avoids the prototype-collision and coercion problems of object-as-dictionary (__proto__, "1" vs 1). Set deduplicates by SameValueZero (so NaN dedupes). WeakMap/WeakSet hold keys weakly — the tool for attaching metadata without retaining objects.
Error Handling
try/catch/finally; finally runs regardless and is where cleanup belongs. Throw Error subclasses so instanceof and stack traces survive; rethrow in catch when you can't actually handle the error.
class ValidationError extends Error {
constructor(field) {
super(`invalid: ${field}`);
this.name = 'ValidationError';
}
}
try {
throw new ValidationError('email');
} catch (err) {
if (err instanceof ValidationError) console.log(err.message);
else throw err;
} finally {
console.log('cleanup');
}Exercise
Why does { a = 1 } = { a: null } yield a === null?
Show answer
Destructuring defaults trigger only on undefined; null is a defined value.
Exercise
How do you deep-freeze an object?
Show answer
Recursively freeze — freeze each own property value that is an object before (or while) freezing the parent. There's no built-in.
Spotted a mistake, or code that does not work?