Episode 1 — Fundamentals / 1.23 — Objects
1.23.d — Adding and Deleting Properties
In one sentence: JavaScript objects are mutable by default — you can add, modify, and delete properties at any time, check if a property exists with
inorObject.hasOwn, and create non-mutating updates with the spread operator orObject.assign.
Navigation: ← 1.23 Overview · 1.23.e — Nested Objects →
1. Adding properties
You can add a new property to an existing object at any time:
const user = { name: "Alice" };
// Dot notation
user.age = 25;
// Bracket notation
user["email"] = "alice@example.com";
// Computed key
const field = "role";
user[field] = "admin";
console.log(user);
// { name: "Alice", age: 25, email: "alice@example.com", role: "admin" }
Adding properties to const objects
const prevents reassignment of the variable, not mutation of the object:
const config = { theme: "light" };
// This works — mutating the object
config.fontSize = 16;
config.theme = "dark";
console.log(config); // { theme: "dark", fontSize: 16 }
// This fails — reassigning the variable
// config = { theme: "blue" }; // TypeError: Assignment to constant variable
2. Modifying existing properties
Overwrite a property by assigning a new value:
const product = {
name: "Widget",
price: 9.99,
inStock: true,
};
product.price = 12.99; // update price
product.inStock = false; // update availability
product.name = "Super Widget"; // update name
console.log(product);
// { name: "Super Widget", price: 12.99, inStock: false }
Conditional updates
const user = { name: "Alice", age: 25, role: "viewer" };
// Update only if condition is met
function updateUser(user, updates) {
for (const [key, value] of Object.entries(updates)) {
if (value !== undefined) {
user[key] = value;
}
}
}
updateUser(user, { role: "admin", age: undefined }); // age stays unchanged
console.log(user); // { name: "Alice", age: 25, role: "admin" }
3. delete operator — removing properties entirely
The delete operator removes a property from an object:
const user = {
name: "Alice",
age: 25,
tempToken: "abc123",
};
delete user.tempToken;
console.log(user); // { name: "Alice", age: 25 }
// tempToken is completely gone
console.log("tempToken" in user); // false
Bracket notation with delete
const headers = {
"Content-Type": "application/json",
"Authorization": "Bearer token123",
"X-Debug": "true",
};
delete headers["X-Debug"];
console.log(headers);
// { "Content-Type": "application/json", "Authorization": "Bearer token123" }
4. delete returns true / false
delete returns a boolean — but the result is not always intuitive:
const obj = { a: 1, b: 2 };
console.log(delete obj.a); // true — property removed
console.log(delete obj.nonExistent); // true — nothing to delete, but no error
console.log(delete obj); // false — cannot delete a variable
// delete on non-configurable properties returns false:
const arr = [1, 2, 3];
console.log(delete arr.length); // false — length is non-configurable
What delete does NOT do
- Does not free memory directly (garbage collector handles that).
- Does not work on variables, function declarations, or non-configurable properties.
- Does not throw errors in non-strict mode (returns
falsesilently).
5. Setting to undefined vs delete — a critical distinction
const user = { name: "Alice", age: 25, role: "admin" };
// Setting to undefined — property STILL EXISTS
user.role = undefined;
console.log(user.role); // undefined
console.log("role" in user); // true — property exists!
console.log(Object.keys(user)); // ["name", "age", "role"] — still listed!
// Using delete — property is REMOVED
delete user.age;
console.log(user.age); // undefined (same output, different reason)
console.log("age" in user); // false — property is gone!
console.log(Object.keys(user)); // ["name", "role"]
Comparison table
obj.prop = undefined | delete obj.prop | |
|---|---|---|
obj.prop result | undefined | undefined |
"prop" in obj | true | false |
Shows in Object.keys | Yes | No |
Shows in for...in | Yes | No |
Shows in JSON.stringify | No (undefined values skipped) | No |
| Property descriptor exists | Yes | No |
When to use each
delete— when the property should truly not exist (cleaning up sensitive data, removing optional fields).= undefined— when you want to clear the value but keep the key in the object's shape (useful for maintaining consistent structure).
6. Property existence checks
Three ways to check if a property exists:
'key' in obj — checks own AND prototype chain
const user = { name: "Alice", age: 25 };
console.log("name" in user); // true
console.log("email" in user); // false
console.log("toString" in user); // true — inherited from Object.prototype!
obj.hasOwnProperty('key') — checks own only
const user = { name: "Alice", age: 25 };
console.log(user.hasOwnProperty("name")); // true
console.log(user.hasOwnProperty("toString")); // false — inherited, not own
Object.hasOwn(obj, 'key') — ES2022, preferred
const user = { name: "Alice", age: 25 };
console.log(Object.hasOwn(user, "name")); // true
console.log(Object.hasOwn(user, "toString")); // false
console.log(Object.hasOwn(user, "email")); // false
Why Object.hasOwn is preferred over hasOwnProperty
// Edge case: object created with no prototype
const bare = Object.create(null);
bare.key = "value";
// bare.hasOwnProperty("key"); // TypeError! — no prototype, no method
console.log(Object.hasOwn(bare, "key")); // true — works on any object
Comparison table
| Method | Checks own? | Checks prototype? | Safe on Object.create(null)? | ES version |
|---|---|---|---|---|
"key" in obj | Yes | Yes | Yes | ES1 |
obj.hasOwnProperty("key") | Yes | No | No | ES3 |
Object.hasOwn(obj, "key") | Yes | No | Yes | ES2022 |
7. Spread operator for non-mutating updates
The spread operator ... creates a shallow copy with overrides — the original is untouched:
const original = { name: "Alice", age: 25, role: "viewer" };
// Create a new object with updated role
const updated = { ...original, role: "admin" };
console.log(updated); // { name: "Alice", age: 25, role: "admin" }
console.log(original); // { name: "Alice", age: 25, role: "viewer" } — unchanged!
Order matters — later properties win
const defaults = { theme: "light", fontSize: 14, language: "en" };
const userPrefs = { theme: "dark", fontSize: 18 };
// User preferences override defaults
const config = { ...defaults, ...userPrefs };
console.log(config);
// { theme: "dark", fontSize: 18, language: "en" }
// Reverse order — defaults would override user preferences (wrong!)
const wrong = { ...userPrefs, ...defaults };
console.log(wrong);
// { theme: "light", fontSize: 14, language: "en" } — user prefs lost!
Removing a property immutably with destructuring + rest
const user = { name: "Alice", age: 25, tempToken: "abc123" };
// Remove tempToken without mutating original
const { tempToken, ...cleanUser } = user;
console.log(cleanUser); // { name: "Alice", age: 25 }
console.log(user); // { name: "Alice", age: 25, tempToken: "abc123" } — unchanged
console.log(tempToken); // "abc123" — captured in its own variable
8. Object.assign for merging (mutating)
Object.assign(target, ...sources) copies properties from sources into the target object:
const target = { a: 1, b: 2 };
const source1 = { b: 3, c: 4 };
const source2 = { d: 5 };
Object.assign(target, source1, source2);
console.log(target); // { a: 1, b: 3, c: 4, d: 5 } — target is MUTATED
Non-mutating pattern — use an empty target
const original = { name: "Alice", age: 25 };
const updates = { age: 26, email: "alice@example.com" };
// New object — original is untouched
const merged = Object.assign({}, original, updates);
console.log(merged); // { name: "Alice", age: 26, email: "alice@example.com" }
console.log(original); // { name: "Alice", age: 25 } — unchanged
Object.assign vs spread
| Feature | Object.assign(target, src) | { ...src1, ...src2 } |
|---|---|---|
| Mutates target? | Yes (unless target is {}) | No (always new object) |
| Return value | The target object | A new object |
| Setters on target | Triggers setters | Does not (copies values) |
| Readability | Longer | Cleaner |
| Recommendation | Use when you need mutation or setter behavior | Preferred for immutable patterns |
9. Real examples
Updating a user profile
const user = {
id: 42,
name: "Alice",
email: "alice@example.com",
bio: "Developer",
avatar: "alice.png",
};
// Mutable update
function updateProfile(user, updates) {
for (const [key, value] of Object.entries(updates)) {
if (Object.hasOwn(user, key)) {
user[key] = value;
}
}
}
updateProfile(user, { bio: "Senior Developer", avatar: "alice-new.png" });
console.log(user.bio); // "Senior Developer"
// Immutable update
function updateProfileImmutable(user, updates) {
return { ...user, ...updates };
}
const updatedUser = updateProfileImmutable(user, { name: "Alice J." });
console.log(updatedUser.name); // "Alice J."
console.log(user.name); // "Alice" — still "Alice" if original wasn't mutated before
Removing optional fields before API submission
const formData = {
username: "alice",
email: "alice@example.com",
phone: "",
bio: "",
avatar: null,
};
// Remove empty/null optional fields
function cleanFormData(data, requiredFields) {
const cleaned = { ...data };
for (const key of Object.keys(cleaned)) {
if (!requiredFields.includes(key) && (cleaned[key] === "" || cleaned[key] === null)) {
delete cleaned[key];
}
}
return cleaned;
}
const payload = cleanFormData(formData, ["username", "email"]);
console.log(payload);
// { username: "alice", email: "alice@example.com" }
// phone, bio, avatar removed — but username and email kept even if empty
Key takeaways
- Add properties anytime:
obj.newKey = valueorobj["new-key"] = value. constprevents variable reassignment, not object mutation.delete obj.propremoves the property entirely;obj.prop = undefinedkeeps the key.- Check existence with
Object.hasOwn(obj, key)(ES2022, safest) or"key" in obj(includes prototype). - Spread
{ ...obj, key: newValue }creates non-mutating updates — the original is preserved. Object.assignmutates the target; useObject.assign({}, obj)for a non-mutating copy.- Destructuring + rest
const { removed, ...rest } = objis the cleanest way to remove a property immutably.
Explain-It Challenge
Explain without notes:
- What is the difference between
delete obj.propandobj.prop = undefined? Show with"prop" in obj. - Why is
Object.hasOwnpreferred overobj.hasOwnProperty? - How does the spread operator create a non-mutating update? What is the limitation with nested objects?
Navigation: ← 1.23 Overview · 1.23.e — Nested Objects →