JavaScript by Example

Objects

Literals, shorthand, computed keys, spread, destructuring, and JSON serialization quirks.

Objects are JavaScript's primary data structure: unordered key-value pairs where keys are strings (or symbols) and values can be anything.

Object literals support shorthand property names, computed keys, and method shorthand. These are purely syntactic - the runtime result is identical to the verbose form.

const name = "Ada";
const score = 98;
 
// Shorthand: { name: name, score: score }
const user = { name, score };
console.log(user); // { name: 'Ada', score: 98 }
 
// Computed key
const field = "role";
const record = { [field]: "admin" };
console.log(record); // { role: 'admin' }
 
// Method shorthand
const counter = {
  count: 0,
  increment() {
    this.count++;
  },
};
counter.increment();
console.log(counter.count); // 1

Destructuring extracts named properties into local variables. Nested destructuring and defaults work too. The spread operator copies enumerable own properties into a new object.

const config = { host: "localhost", port: 5432, db: "main" };
 
// Destructure with rename and default
const { host, port: dbPort, ssl = false } = config;
console.log(host);   // localhost
console.log(dbPort); // 5432
console.log(ssl);    // false
 
// Spread: shallow copy with overrides
const devConfig = { ...config, port: 5433, db: "dev" };
console.log(devConfig);
// { host: 'localhost', port: 5433, db: 'dev' }
 
// Original untouched
console.log(config.port); // 5432

JSON.stringify has three silent surprises: it drops undefined values and function properties, it throws on circular references, and Date objects become ISO strings (not re-parsed by JSON.parse).

const data = {
  id: 1,
  name: "Alice",
  password: undefined,  // dropped
  greet() { return "hi"; }, // dropped
  created: new Date("2024-01-01"),
};
 
const json = JSON.stringify(data);
console.log(json);
// {"id":1,"name":"Alice","created":"2024-01-01T00:00:00.000Z"}
 
// Circular reference throws
const a = {};
a.self = a;
try {
  JSON.stringify(a);
} catch (e) {
  console.log(e.message); // Converting circular structure to JSON
}
 
// JSON.parse does NOT restore Date objects - you get a string
const parsed = JSON.parse(json);
console.log(typeof parsed.created); // string

Object.freeze makes an object non-configurable and non-writable - but only one level deep. Nested objects are still mutable. structuredClone creates a true deep copy of most serializable values.

const settings = Object.freeze({ theme: "dark", limits: { max: 10 } });
 
settings.theme = "light"; // silently ignored (strict mode throws)
console.log(settings.theme); // dark - unchanged
 
settings.limits.max = 999; // nested object is NOT frozen
console.log(settings.limits.max); // 999
 
// Deep copy with structuredClone (Node 17+, modern browsers)
const original = { user: { name: "Bo", tags: ["a", "b"] } };
const copy = structuredClone(original);
copy.user.name = "Cal";
console.log(original.user.name); // Bo - truly independent

In production

The spread operator {...obj} copies only the top-level properties - nested objects are still shared by reference. Teams that treat spread as a deep clone and then mutate a nested field discover the bug when two callers observe each other's changes. For truly independent copies, use structuredClone (available in Node 17+ and modern browsers) or a library like Immer for immutable update patterns. TypeScript's Readonly<T> and as const give compile-time protection but do nothing at runtime - they catch the mistake in code review, not in production.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free