Object Basics
An object is a collection of key-value pairs. Keys are strings (or Symbols); values can be any type, including other objects and functions.
// Object literal
const user = {
name: "Alice",
age: 28,
isAdmin: false,
address: {
city: "London",
zip: "SW1A 1AA"
}
};
// Read properties - dot notation (preferred)
console.log(user.name); // "Alice"
console.log(user.address.city); // "London"
// Bracket notation - use when key is dynamic or has spaces
console.log(user["age"]); // 28
const key = "name";
console.log(user[key]); // "Alice"
const data = { "first name": "Bob" };
console.log(data["first name"]); // "Bob"
// Set and update properties
user.email = "alice@example.com"; // add
user.age = 29; // update
delete user.isAdmin; // remove
console.log(user);
// Check property existence
console.log("name" in user); // true
console.log(Object.hasOwn(user, "age")); // true (ES2022)
console.log(user.phone !== undefined); // false (but unsafe if prop is undefined intentionally)
Property Shorthand and Computed Keys
ES6 introduced shorthand syntax that eliminates repetitive code when building objects:
const name = "Alice";
const age = 28;
// Property shorthand: when variable name matches key name
const user = { name, age }; // same as { name: name, age: age }
console.log(user); // { name: "Alice", age: 28 }
// Method shorthand
const calculator = {
value: 0,
add(n) { this.value += n; return this; }, // shorthand method
subtract(n) { this.value -= n; return this; },
result() { return this.value; }
};
calculator.add(10).add(5).subtract(3);
console.log(calculator.result()); // 12
// Computed property names - dynamic keys
const field = "email";
const contact = {
[field]: "alice@example.com", // key = value of 'field'
[`${field}_verified`]: true, // template literal key
};
console.log(contact.email); // "alice@example.com"
console.log(contact.email_verified); // true
// Building dynamic objects with computed keys
function makeField(name, value) {
return { [name]: value };
}
console.log(makeField("score", 100)); // { score: 100 }
Destructuring
Destructuring extracts properties from an object into named variables in a single line.
const user = { name: "Alice", age: 28, city: "London" };
// Basic destructuring
const { name, age } = user;
console.log(name); // "Alice"
console.log(age); // 28
// Rename while destructuring
const { name: userName, age: userAge } = user;
console.log(userName); // "Alice"
// Default values
const { name: n, role = "user" } = user;
console.log(role); // "user" (not on user object, so default kicks in)
// Nested destructuring
const config = { db: { host: "localhost", port: 5432 } };
const { db: { host, port } } = config;
console.log(host); // "localhost"
console.log(port); // 5432
// Rest in destructuring
const { name: name2, ...rest } = user;
console.log(name2); // "Alice"
console.log(rest); // { age: 28, city: "London" }
// Destructuring in function parameters
function greet({ name, age = 0 }) {
return `${name} is ${age} years old`;
}
console.log(greet(user)); // "Alice is 28 years old"
Spread and Merge
The spread operator ... creates a shallow copy or merges objects. Later
properties overwrite earlier ones.
const defaults = { theme: "light", language: "en", fontSize: 16 };
const userPrefs = { theme: "dark", fontSize: 18 };
// Merge: right side overwrites duplicates
const settings = { ...defaults, ...userPrefs };
console.log(settings);
// { theme: "dark", language: "en", fontSize: 18 }
// Shallow copy (modifying copy does not affect original)
const copy = { ...defaults };
copy.theme = "system";
console.log(defaults.theme); // "light" - original unchanged
// Add/override while copying
const updated = { ...defaults, theme: "dark", newProp: true };
// Deep copy - use structuredClone (ES2022)
const nested = { a: 1, b: { c: 2 } };
const shallow = { ...nested };
shallow.b.c = 99; // modifies the original!
console.log(nested.b.c); // 99 (shared reference!)
const deep = structuredClone(nested); // true deep copy
deep.b.c = 999;
console.log(nested.b.c); // 2 - original intact
When you spread an object, nested objects are still shared by reference. If you modify copy.nestedObj.property, it also modifies original.nestedObj.property. For a true independent clone, use structuredClone(obj) (modern) or JSON.parse(JSON.stringify(obj)) (older - does not handle Date, undefined, functions).
Optional Chaining on Objects
const user = {
name: "Alice",
address: {
city: "London"
}
};
// Without optional chaining - crashes if address is missing
// const zip = user.location.zip; // TypeError!
// With optional chaining (?.)
console.log(user?.address?.city); // "London"
console.log(user?.phone?.number); // undefined (no crash)
// On methods
console.log(user?.getFullName?.()); // undefined (method does not exist)
// Combined with nullish coalescing for defaults
const city = user?.address?.city ?? "Unknown";
console.log(city); // "London"
const zip = user?.address?.zip ?? "N/A";
console.log(zip); // "N/A"
Object Static Methods
const user = { name: "Alice", age: 28, city: "London" };
// Object.keys - array of property names
console.log(Object.keys(user)); // ["name","age","city"]
// Object.values - array of property values
console.log(Object.values(user)); // ["Alice",28,"London"]
// Object.entries - array of [key, value] pairs
console.log(Object.entries(user));
// [["name","Alice"],["age",28],["city","London"]]
// Iterate entries
for (const [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`);
}
// Object.fromEntries - convert entries back to object
const doubled = Object.fromEntries(
Object.entries({ a: 1, b: 2, c: 3 }).map(([k, v]) => [k, v * 2])
);
console.log(doubled); // { a: 2, b: 4, c: 6 }
// Object.assign - copy properties (mutates target)
const target = { a: 1 };
Object.assign(target, { b: 2 }, { c: 3 });
console.log(target); // { a: 1, b: 2, c: 3 }
// Object.freeze - prevent modification
const config = Object.freeze({ PORT: 3000, HOST: "localhost" });
config.PORT = 9999; // silently ignored
console.log(config.PORT); // 3000
Nested Objects and Arrays of Objects
const company = {
name: "RubanTech",
employees: [
{ id: 1, name: "Alice", dept: "Engineering", skills: ["JS","Python"] },
{ id: 2, name: "Bob", dept: "Design", skills: ["Figma","CSS"] },
{ id: 3, name: "Carol", dept: "Engineering", skills: ["JS","Go"] },
]
};
// Access nested data
console.log(company.employees[0].name); // "Alice"
console.log(company.employees[0].skills[1]); // "Python"
// Filter and map nested arrays
const engineers = company.employees
.filter(e => e.dept === "Engineering")
.map(e => e.name);
console.log(engineers); // ["Alice","Carol"]
// Find and destructure
const { name: empName, skills } = company.employees.find(e => e.id === 1);
console.log(empName); // "Alice"
console.log(skills); // ["JS","Python"]
Common Object Patterns
// 1. Options object pattern (named parameters)
function createUser({ name, age = 0, role = "user" } = {}) {
return { name, age, role, createdAt: new Date() };
}
const user = createUser({ name: "Alice", age: 28 });
// 2. Immutable update pattern (React / Redux style)
const state = { count: 0, user: "Alice" };
const newState = { ...state, count: state.count + 1 };
// 3. Lookup table (replaces long if/else chains)
const statusLabels = {
200: "OK",
404: "Not Found",
500: "Internal Server Error",
};
const label = statusLabels[404] ?? "Unknown";
console.log(label); // "Not Found"
// 4. Object as namespace
const MathUtils = {
square: x => x ** 2,
cube: x => x ** 3,
clamp: (n, min, max) => Math.min(Math.max(n, min), max)
};
console.log(MathUtils.square(5)); // 25