Objects

Read, reshape and copy objects — and understand references.

12of 32
≈ 12Min read
9Parts

Properties

two ways in, and when each is right
const door = { name: "Painted", "lead time": 5, days: 5 };

door.name;            // dot — when you know the key and it is a valid name
door["lead time"];    // brackets — spaces, dashes, or a key in a variable

const key = "days";
door[key];            // 5

// shorthand when the variable name is the key
const name = "Veneer";
const d = { name };   // { name: "Veneer" }

// a computed key
const field = "days";
const patch = { [field]: 10 };

Missing properties

undefined is not an error
door.missing;              // undefined
door.missing.length;       // TypeError — this is the one that crashes

door.missing?.length;      // undefined
door.missing ?? 0;         // 0

"days" in door;                                   // true, includes inherited
Object.hasOwn(door, "days");                      // true, own only
Object.keys(door).includes("days");

Destructuring

pull out what you need
const { name, days } = door;
const { name: title } = door;                   // rename
const { days = 0 } = door;                      // default, only for undefined
const { address: { city } = {} } = user;        // nested, with a guard
const { name, ...rest } = door;                 // the rest as a new object

// in a parameter list, with a default for the whole thing
function render({ name, days = 0 } = {}) { }
The nested default matters

const { address: { city } } = user throws if address is undefined. Adding = {} makes it undefined instead of a crash.

Spread and merging

Spread is shallow. { ...user } gives you a new object holding the same nested objects, so copy.address.city = "x" changes the original's address too.

later keys win
const patched = { ...door, days: 10 };
const merged = { ...defaults, ...options };

// removing a key
const { archived, ...withoutArchived } = door;

// conditional properties
const body = {
  name,
  ...(days ? { days } : {}),
};

References

the mental model that prevents most object bugs
const a = { n: 1 };
const b = a;
b.n = 2;
a.n;                  // 2 — one object, two names

{ n: 1 } === { n: 1 }   // false — different objects
a === b                 // true  — the same object

// which is why this "does nothing"
function reset(obj) { obj = {}; }       // reassigns the local name only
function clear(obj) { obj.n = 0; }      // changes the object

Copying

three depths
const shallow = { ...obj };                  // one level
const shallow2 = Object.assign({}, obj);     // the same thing, older

const deep = structuredClone(obj);           // Dates, Maps, Sets — but NOT functions

// the old JSON round trip: loses Dates, undefined, functions, Infinity
const lossy = JSON.parse(JSON.stringify(obj));

Walking an object

keys, values, entries
Object.keys(door);        // ["name","days"]
Object.values(door);      // ["Painted", 5]
Object.entries(door);     // [["name","Painted"], ["days", 5]]

for (const [k, v] of Object.entries(door)) { }

// transform every value
Object.fromEntries(
  Object.entries(door).map(([k, v]) => [k, String(v)])
);

Freezing and sealing

when you want it not to change
const config = Object.freeze({ retries: 3 });
config.retries = 5;          // silently ignored (throws in strict mode)

Object.isFrozen(config);     // true
// freeze is shallow: config.nested can still be changed

Objects as lookups

and when to reach for a Map instead
const days = { painted: 5, veneer: 10 };
days[kind] ?? 12;

// a Map when: the keys are not strings, you need an order, or you add and
// remove a lot — and it has a size, which an object does not
const seen = new Map();
seen.set(element, { count: 1 });
seen.get(element);
seen.has(element);
seen.size;
Objects inherit keys

({}).toString exists. A lookup built from user input can collide with constructor or __proto__. Object.create(null) or a Map has no such inheritance.

Practice