Objects, Properties & Destructuring

This section documents modern ECMAScript and core browser JavaScript APIs — it is not tied to any specific framework or library (React, Vue, Angular, etc.). This content was generated with the assistance of AI and should be verified against the current ECMAScript specification and MDN documentation before relying on it in production, since JavaScript language features and browser API support continue to evolve.

This section’s bibliography lists the reference material consulted while preparing these pages.

Objects are JavaScript’s most fundamental composite datatype: an unordered collection of named properties, each mapping a string (or Symbol) key to a value. This page covers how to create objects, read and write their properties, enumerate them, and how modern destructuring syntax pulls values back out of objects (and arrays) concisely.

Object Literal Syntax

The simplest way to create an object is an object literal — a comma-separated list of key: value pairs enclosed in curly braces:

let empty = {};                        // An object with no properties
let point = { x: 0, y: 0 };            // Two numeric properties
let book = {
  "main title": "JavaScript",          // Non-identifier names need string literals
  author: {                            // Property values can themselves be objects
    firstName: "David",
    surname: "Flanagan",
  },
};

Objects can also be created with new (calling a constructor, e.g. new Object(), new Map()) or with Object.create(proto), which creates a new object using its argument as the object’s prototype — the object it inherits properties from. Passing null creates an object with no prototype at all (not even inherited methods like toString()); passing Object.prototype creates a plain object equivalent to {}.

Every JavaScript object (except one created with Object.create(null)) has a prototype, and property lookups that miss on the object itself continue up this prototype chain until the property is found or the chain ends. Assignment, by contrast, never walks the chain — setting a property always creates or updates an own property on the object itself, leaving any same-named inherited property on the prototype untouched. This distinction is foundational to how classes work under the hood, since every class instance is, at its core, an object that inherits from its class’s prototype.

Property Access: Dot vs. Bracket Notation

Properties are read and written with either the dot operator or square brackets:

let author = book.author;          // Dot notation: righthand side is a literal identifier
let title = book["main title"];    // Bracket notation: righthand side is any string expression

book.edition = 7;                  // Create/set via dot notation
book["main title"] = "ECMAScript"; // Create/set via bracket notation

Dot notation requires the property name to be a fixed identifier typed literally into the source. Bracket notation accepts any expression that evaluates to a string (or a value coercible to one, or a Symbol), which means the property name can be computed at runtime — exactly what you need when iterating over an object as an associative array:

function addStock(portfolio, stockName, shares) {
  portfolio[stockName] = shares; // stockName isn't known until runtime
}

Querying a property that doesn’t exist evaluates to undefined rather than throwing — but accessing any property of null/undefined throws a TypeError. Optional chaining (?., covered on Functions, Expressions & Operators) guards against this: book?.author?.surname evaluates to undefined instead of throwing if book or book.author is missing.

Deleting a property (removing it entirely, not just setting it to undefined) uses the delete operator, which only ever removes own properties:

delete book.author; // book no longer has an "author" property

Computed Property Names

Wrapping an expression in square brackets inside an object literal computes the property name at the point the literal is evaluated, rather than requiring a two-step "create the object, then assign the property" sequence:

const PROPERTY_NAME = "p1";
function computePropertyName() {
  return "p" + 2;
}

let p = {
  [PROPERTY_NAME]: 1,
  [computePropertyName()]: 2,
};
p.p1 + p.p2 // => 3

Computed property names are also how you use a Symbol as a property key — useful for adding properties to an object without risking a name collision with properties added by other code, since every Symbol is unique:

const extension = Symbol("my extension");
let o = { [extension]: { /* ... */ } };

Shorthand Properties & Methods

When a variable’s name should match the property name it’s assigned to, the key: value pair can be shortened to just the identifier:

let x = 1, y = 2;
let o = { x, y };       // Same as { x: x, y: y }

Methods defined on an object literal can similarly drop the function keyword and the colon:

let square = {
  side: 10,
  area() {               // Same as area: function() { ... }
    return this.side * this.side;
  },
};

Property names in either shorthand form can be any legal object-literal key, including string literals and computed/Symbol names via the bracket syntax above.

Property Enumeration

The for…​in loop iterates once per enumerable property (own or inherited) of an object, assigning each property’s name to the loop variable — see Statements for the loop syntax itself:

let o = { x: 1, y: 2, z: 3 };
for (const key in o) {
  console.log(key); // "x", "y", "z" -- inherited built-in methods are non-enumerable, so they're skipped
}

More often, it’s simpler to get an array of property names/values/pairs up front and iterate that with for…​of (see Iterators & Generators):

const point = { x: 1, y: 2 };

Object.keys(point);    // => ["x", "y"] -- enumerable own property names
Object.values(point);  // => [1, 2] -- enumerable own property values
Object.entries(point); // => [["x", 1], ["y", 2]] -- [key, value] pairs

for (const [key, value] of Object.entries(point)) {
  console.log(`${key} = ${value}`);
}

Object.getOwnPropertyNames() additionally returns non-enumerable own property names, and Reflect.ownKeys() returns every own property key, enumerable or not, string or Symbol — see Standard Library: Metaprogramming for the full property- descriptor picture (writable/enumerable/configurable attributes).

To copy properties from one or more objects into another, Object.assign(target, …​sources) copies each source’s own enumerable properties into target (later sources win on conflicts) and returns target:

let merged = Object.assign({}, defaults, overrides); // overrides wins over defaults

Spread in Object Literals

The …​ spread syntax copies another object’s own enumerable properties directly into a new object literal, which is usually clearer than Object.assign() for this exact "merge with override" pattern:

let position = { x: 0, y: 0 };
let dimensions = { width: 100, height: 75 };
let rect = { ...position, ...dimensions }; // { x: 0, y: 0, width: 100, height: 75 }

let overridden = { ...defaults, ...overrides }; // properties in overrides win on conflict

Like Object.assign(), spreading only copies own properties — anything the source object only inherits from its prototype is left out. Spreading n properties is an O(n) operation, so accumulating into one large object via …​ inside a loop can quietly become O(n^2).

Object & Array Destructuring

Destructuring assignment unpacks values out of an object or array into individual variables in a single statement, mirroring the shape of the object/array literal on the lefthand side of =.

Object Destructuring

const point = { x: 1, y: 2, z: 3 };

const { x, y } = point;        // x === 1, y === 2 (z is simply not extracted)
const { x: px, y: py } = point; // Rename while destructuring: px === 1, py === 2
const { w = 0 } = point;        // Default value used when the property is missing: w === 0

Array Destructuring

Array destructuring matches by position rather than by key, and can skip elements with an empty slot:

const rgb = [255, 128, 0];
const [r, g, b] = rgb;        // r === 255, g === 128, b === 0
const [first, , third] = rgb; // Skip the middle element: first === 255, third === 0
const [head, ...rest] = rgb;  // Rest pattern: head === 255, rest === [128, 0]

Nested Destructuring

Patterns can nest arbitrarily deep to reach into nested structures in one expression:

const response = {
  status: 200,
  body: { user: { name: "Ada", roles: ["admin", "editor"] } },
};

const {
  body: {
    user: { name, roles: [primaryRole] },
  },
} = response;
// name === "Ada", primaryRole === "admin"

Destructuring in Function Parameters

The same object/array patterns can appear directly in a function’s parameter list, which is the idiomatic way to accept an options object with named, defaultable fields — see Function Parameters & Namespaces for the full parameter- handling picture (default values, rest parameters, and how destructured parameters combine with them):

function drawCircle({ x = 0, y = 0, radius = 1 } = {}) {
  // ...
}

drawCircle({ x: 10, y: 10, radius: 5 });
drawCircle(); // Every field falls back to its default

Bibliography