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