JavaScript Objects and Destructuring

AI-assistedDrafted with AI assistance and reviewed before publishing; examples are version-pinned. Verify against official docs when in doubt.

Node.js 22 LTS

Objects are how JavaScript groups related data. This chapter covers the basics, destructuring, and the reference semantics behind “I changed one object and another one changed too”.

Literals and property access

const user = {
  name: "ann",
  age: 30,
  active: true,
};

Two ways to read a property:

user.name;         // dot: keys that are valid identifiers
user["name"];      // brackets: dynamic keys, spaces, hyphens
const key = "age";
user[key];                 // 30
user["date-of-birth"];     // dot syntax cannot express this

A missing property returns undefined rather than throwing:

user.city;         // undefined
user.city.name;    // TypeError: Cannot read properties of undefined

That second line is why optional chaining exists: user.city?.name returns undefined instead of throwing.

Shorthand and computed keys

const name = "ann";
const age = 30;

const user = { name, age };          // same as { name: name, age: age }

const field = "role";
const withRole = { name, [field]: "admin" };
// { name: "ann", role: "admin" }

Methods have a shorthand too:

const counter = {
  count: 0,
  inc() {          // same as inc: function () {}
    this.count += 1;
  },
};

Method shorthand is a regular function, so this is the calling object. An arrow function here would inherit this from the surrounding scope and the method would not work.

Add, update, delete

const user = { name: "ann" };

user.age = 30;                 // add
user.name = "anna";            // update
delete user.age;               // remove

Object.hasOwn(user, "age");    // false

Object.hasOwn(obj, key) checks own properties only. Use key in obj when inherited properties should count — rarely what you want for plain data.

Iterating an object

const user = { name: "ann", age: 30, active: true };

Object.keys(user);      // ["name", "age", "active"]
Object.values(user);    // ["ann", 30, true]
Object.entries(user);   // [["name", "ann"], ["age", 30], ["active", true]]

Converting to an array and using array methods is the most flexible approach:

Object.entries(user)
  .filter(([, value]) => typeof value === "string")
  .map(([key, value]) => `${key}=${value}`);
// ["name=ann"]

Ordering follows the specification: string keys keep insertion order, while integer-like keys ("2", "10") come first in ascending numeric order. Do not rely on object key order for user-visible sorting; sort the entries explicitly.

Destructuring

const user = { name: "ann", age: 30, city: "Shenzhen" };
const { name, age } = user;
console.log(name, age);   // "ann" 30

Renaming and defaults:

const { name: userName, role = "user" } = user;
console.log(userName);   // "ann"
console.log(role);       // "user"

Nesting:

const order = { id: 1, buyer: { name: "bob" } };
const { buyer: { name } } = order;   // "bob"

Nested destructuring throws if the intermediate value is missing. Default the intermediate to an empty object:

const { buyer: { name } = {} } = order;

Arrays destructure by position, which is handy for swapping and skipping:

const [first, second] = [1, 2, 3];
const [, , third] = [1, 2, 3];       // 3
[first, second] = [second, first];   // swap

Destructuring in function parameters is covered in the functions chapter; it is the standard way to accept an options object.

Spread and shallow copies

const base = { name: "ann", role: "user" };
const admin = { ...base, role: "admin" };

console.log(admin);   // { name: "ann", role: "admin" }
console.log(base);    // { name: "ann", role: "user" }

Later properties win. This “new object from an old one” pattern is how immutable updates work in React-style code.

Spread is shallow. Nested objects are still shared:

const user = { name: "ann", address: { city: "Shenzhen" } };
const copy = { ...user };

copy.address.city = "Beijing";
console.log(user.address.city);   // "Beijing"

To copy one level deeper without the reference sharing, spread at each level you intend to modify:

const updated = { ...user, address: { ...user.address, city: "Beijing" } };

For a full independent copy, use structuredClone() (Node 17+, all modern browsers):

const deepCopy = structuredClone(user);
deepCopy.address.city = "Beijing";
console.log(user.address.city);   // "Shenzhen"

structuredClone handles dates, maps, sets, and cyclic references, but not functions or class prototypes.

Reference semantics

const a = { count: 1 };
const b = a;
b.count = 2;
console.log(a.count);   // 2

b = a copies the reference; both names point at one object. Comparison behaves the same way:

{ count: 1 } === { count: 1 }   // false, two objects
a === b                          // true, same object

For structural comparison, compare fields, or serialize both sides when key order is stable. JSON.stringify comparison is fragile; for real needs use a deep-equal helper.

Freezing

const config = Object.freeze({ retries: 3 });
config.retries = 5;      // TypeError in strict mode, silent no-op otherwise
console.log(config.retries);   // 3

Object.freeze is shallow, so nested objects stay mutable. It also does not prevent { ...config, retries: 5 }, which is exactly how immutable updates are supposed to work.

Common mistakes

1. Assuming assignment copies

const b = a aliases the object. Copy with spread, or structuredClone for nested data.

2. Treating spread as a deep copy

Nested objects are shared; mutating them leaks across copies.

3. Destructuring a missing nested object

const { a: { b } } = obj throws when a is undefined. Provide = {}.

4. for...in without filtering

Inherited enumerable properties show up. Use Object.keys() or Object.entries().

5. Comparing objects with ===

Always false unless it is the same reference.

6. Using an object as a map with arbitrary string keys

obj["__proto__"] and obj["constructor"] collide with the prototype. Use Map or Object.create(null) for untrusted keys.

Summary

  • Dot access for fixed keys, brackets for dynamic ones.
  • Iterate with Object.keys() / values() / entries() and then use array methods.
  • Destructuring supports renaming, defaults, and nesting.
  • Spread copies one level; structuredClone() copies deeply.
  • Assignment and === work on references, not contents.

Next: strings, template literals, and text handling.