Properties
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
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
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.
const patched = { ...door, days: 10 };
const merged = { ...defaults, ...options };
// removing a key
const { archived, ...withoutArchived } = door;
// conditional properties
const body = {
name,
...(days ? { days } : {}),
};
References
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
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
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
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
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.