JavaScript Objects
An object is an unordered collection of key/value pairs. Keys (also called properties) are strings or Symbols; values can be anything. Objects are JavaScript’s universal data carrier — every array, function, class instance, and even modules are objects underneath. This chapter covers literals, access patterns, shorthand syntax, computed keys, and a first look at this.
Creating Objects
// Literal — by far the most common
const user = {
name: "Ada",
age: 37,
active: true,
};
// Empty + assign
const empty = {};
empty.name = "Lin";
// Object.create — with a specific prototype
const fromProto = Object.create(null); // no prototype at all
// Object constructor — rarely needed
const obj = new Object();
Tip: Use object literals ({}) 99% of the time. Object.create(null) is occasionally useful for “pure dictionaries” with no inherited methods.
Properties — Keys and Values
const user = {
name: "Ada", // shorthand: key is "name", value is "Ada"
"favourite color": "blue", // quoted key — needed if key has spaces or starts with digit
42: "the answer", // numeric keys are coerced to strings
};
user.name // "Ada"
user["favourite color"] // "blue"
user[42] // "the answer"
user["42"] // "the answer" ← same key
Rules for unquoted keys: must be a valid identifier (letters, digits, _, $, no leading digit). Otherwise quote them.
Dot vs Bracket Access
const obj = { name: "Ada", "first-name": "Ada" };
obj.name // "Ada" ← dot
obj["name"] // "Ada" ← bracket, same thing
obj["first-name"] // "Ada" ← must use bracket (hyphen)
obj.first-name // ❌ ReferenceError — parsed as obj.first - name
// Dynamic key from a variable
const key = "name";
obj[key]; // "Ada"
obj.key; // undefined ← literal property named "key"
| Use dot | Use bracket |
|---|---|
| Static, valid-identifier key | Dynamic key (from a variable) |
obj.name |
obj[someKey] |
| Most readable | Required for spaces, hyphens, digits |
Missing Properties
const obj = { name: "Ada" };
obj.age // undefined — no error
obj.profile.bio // ❌ TypeError — can't read .bio of undefined
// Safe with optional chaining (ES2020)
obj.profile?.bio // undefined — short-circuits
// Default via nullish coalescing
obj.age ?? 0 // 0
obj.name ?? "n/a" // "Ada"
// Checking existence
"name" in obj // true — includes inherited
obj.hasOwnProperty("name") // true — own only (older form)
Object.hasOwn(obj, "name") // true — own only (modern, ES2022)
Warning: Reading a missing property is silent (undefined); reading a property off undefined throws. Use ?. to traverse paths that might be incomplete.
Methods — Functions on Objects
const calc = {
// Method shorthand (preferred)
add(a, b) {
return a + b;
},
// Equivalent classic form
multiply: function (a, b) {
return a * b;
},
// Arrow — beware: no own `this`
greet: () => "hello",
};
calc.add(2, 3); // 5
calc.multiply(4, 5); // 20
calc.greet(); // "hello"
A First Look at this
Inside a regular function called as a method, this refers to the object the method was called on.
const user = {
name: "Ada",
greet() {
return `Hi, I'm ${this.name}`;
},
};
user.greet(); // "Hi, I'm Ada"
But this is dynamic — it depends on how the function is called, not where it was defined:
const greet = user.greet;
greet(); // "Hi, I'm undefined" ← lost the binding
// Arrow methods don't get their own `this`
const broken = {
name: "Ada",
greet: () => `Hi, I'm ${this.name}`,
};
broken.greet(); // "Hi, I'm undefined" ← `this` is from outer scope
Rule of thumb: method shorthand for object methods, arrow for callbacks. Full coverage in chapter 29.
Warning: Don’t write methods as arrow functions on object literals. They will not bind this to the object — they’ll capture the surrounding scope’s this.
Shorthand Property Names
When the variable name and key are the same:
const name = "Ada";
const age = 37;
// Old
const user = { name: name, age: age };
// Shorthand (ES2015)
const user2 = { name, age };
// → { name: "Ada", age: 37 }
Computed Property Names
Use a bracket inside the literal to compute the key dynamically:
const field = "email";
const value = "ada@example.com";
const user = {
name: "Ada",
[field]: value, // key is "email"
[`is${"Active"}`]: true, // key is "isActive"
};
// → { name: "Ada", email: "ada@example.com", isActive: true }
Very useful in reduce:
["a", "b", "c"].reduce((acc, k, i) => ({ ...acc, [k]: i }), {});
// { a: 0, b: 1, c: 2 }
Modifying, Adding, Deleting
const obj = { name: "Ada", age: 37 };
obj.age = 38; // modify
obj.email = "a@b.com"; // add
delete obj.age; // remove
console.log(obj); // { name: "Ada", email: "a@b.com" }
delete is the only way to remove a property. (Setting to undefined keeps the key but with value undefined — different from absent.)
Iterating an Object
There is no forEach directly on objects. The standard idioms:
const user = { name: "Ada", age: 37, role: "admin" };
// Keys
Object.keys(user); // ["name", "age", "role"]
// Values
Object.values(user); // ["Ada", 37, "admin"]
// [key, value] pairs
Object.entries(user); // [["name", "Ada"], ["age", 37], ["role", "admin"]]
// for…in — also walks inherited enumerable string keys
for (const key in user) {
console.log(key, user[key]);
}
// Modern preferred form: destructure entries
for (const [key, value] of Object.entries(user)) {
console.log(key, value);
}
| Want | Use |
|---|---|
| Keys as an array | Object.keys(obj) |
| Values as an array | Object.values(obj) |
| Pairs as an array | Object.entries(obj) |
Iterate with break |
for…of Object.entries(obj) |
Tip: Property iteration order is insertion order for string keys, and integer-like keys come first in numeric order. Don’t rely on key order for correctness, but it is predictable.
Copying and Merging Objects
Both copy shallowly — nested objects are still shared by reference.
const original = { a: 1, b: 2 };
// Spread (most readable)
const copy = { ...original };
const merged = { ...original, b: 99, c: 3 };
// → { a: 1, b: 99, c: 3 }
// Object.assign (mutates first argument)
const merged2 = Object.assign({}, original, { c: 3 });
Deep copy when needed:
// Modern
const deep = structuredClone(original);
// Older, JSON-safe only (loses functions, dates → strings, etc.)
const deepJson = JSON.parse(JSON.stringify(original));
Warning: Spread and Object.assign only copy the top level. {...a} does NOT clone nested objects — they remain shared. Reach for structuredClone for a real deep copy.
Object Destructuring (Preview)
const user = { name: "Ada", age: 37, role: "admin" };
// Extract
const { name, age } = user;
// name = "Ada", age = 37
// Rename
const { name: userName } = user;
// userName = "Ada"
// Default
const { country = "Unknown" } = user;
// Rest
const { name: n, ...rest } = user;
// n = "Ada", rest = { age: 37, role: "admin" }
Full chapter is 33.
Reference vs Value
Objects are passed by reference (technically, “by sharing”):
const a = { count: 1 };
const b = a;
b.count = 99;
console.log(a.count); // 99 ← same object!
a === b; // true
a === { count: 99 }; // false ← different object, even with same contents
// To compare contents you need a deep compare or JSON serialise
JSON.stringify(a) === JSON.stringify({ count: 99 }); // true (cheap & dirty)
A Realistic Example — Config Merger
const defaults = {
retries: 3,
timeoutMs: 5000,
headers: { "Content-Type": "application/json" },
};
function fetchWith(overrides = {}) {
const opts = {
...defaults,
...overrides,
headers: { ...defaults.headers, ...(overrides.headers ?? {}) },
};
console.log(opts);
// ...actual fetch
}
fetchWith({ retries: 5, headers: { "X-Trace": "abc" } });
// {
// retries: 5,
// timeoutMs: 5000,
// headers: { "Content-Type": "application/json", "X-Trace": "abc" },
// }
Note the explicit deep merge of headers — a flat spread alone would have replaced the whole nested object.
Best Practices Checklist
- ✅ Use object literals for almost every case
- ✅ Use method shorthand (
greet()) for object methods - ✅ Use shorthand and computed keys when they improve readability
- ✅ Use
Object.hasOwn(obj, key)(ES2022) to test for own properties - ✅ Use
?.and??to safely read possibly-missing values - ✅ Use
structuredClonewhen you need a real deep copy - ❌ Don’t use arrow functions as methods that need
this - ❌ Don’t rely on shallow spread to copy nested objects
- ❌ Don’t iterate with
for…inif you only want own keys — useObject.keys/entries
What’s Next?
You can build, read, and modify objects. Next, the static methods on Object — keys, values, entries, freeze, assign, fromEntries, and friends.