Working with Objects (Advanced)
You already know how to create objects, read their properties, and write methods. Now let's learn the power tools 🧰 that professional developers use every day with objects.
Here's what's in this lesson:
| # | Topic | In one line |
|---|---|---|
| 1 | Computed properties | Create keys from variables |
| 2 | Object destructuring | Unpack properties into variables |
| 3 | Optional chaining | Read deep values safely |
| 4 | Getters and setters | Properties that run code |
| 5 | Object copying | Make a copy instead of sharing |
| 6 | Shallow vs deep copy | How deep the copy goes |
| 7 | Object.keys/values/entries | Turn objects into arrays |
| 8 | Property descriptors | Hidden settings of every property |
| 9 | Symbols | Truly unique keys |
1. Computed Properties
Normally, you write a key directly:
const user = { name: "Priya" };
But what if the key name is stored in a variable, or needs to be built at runtime? Use square brackets [ ] around the key. This is called a computed property.
Think of it like a name slip 🏷️: instead of writing the label directly on the box, you write "use whatever name is on this slip."
Building keys with expressions
Real example: updating a form field 📝
One function that can update any field, based on which input changed:
2. Object Destructuring
Destructuring means unpacking properties from an object into separate variables, in one line.
Think of opening a tiffin box 🍱 and putting the roti, dal, and sabzi on separate plates.
Without destructuring
const student = { name: "Aarav", age: 20, city: "Delhi" };
const name = student.name;
const age = student.age;
const city = student.city;
With destructuring ✨
Key difference from arrays: Array destructuring works by position. Object destructuring works by key name, so the order doesn't matter!
Renaming variables
Use key: newName to give the variable a different name.
Default values
If a property is missing, use a default value with =.
Collecting the rest with ...
Destructuring nested objects
Destructuring in function parameters ⭐
This is very common in real projects. You can unpack an object right in the function's brackets.
3. Optional Chaining ?.
When you read a value deep inside an object, a missing part in the middle causes an error. 💥
Optional chaining ?. says: "Go further only if this part exists. Otherwise, stop and give undefined."
It's like asking a security guard 💂 at each gate: "Is this gate open?" If not, you simply turn back, instead of crashing into it.
With a default value ??
With arrays and methods
| Syntax | Use for |
|---|---|
obj?.key | A property |
obj?.[key] | A property with brackets, or an array item |
obj.method?.() | A method that might not exist |
⚠️ Don't use
?.everywhere. Use it only where a value might really be missing, such as data coming from a server.
4. Getters and Setters
Sometimes you want a property that runs some code when you read it or change it. That's what getters and setters do.
| Keyword | Runs when you... | |
|---|---|---|
| Getter | get | Read the property |
| Setter | set | Change the property |
Getters: calculated properties
Notice: you read student.fullName like a normal property, not like a method fullName().
Setters: checking values before saving ✅
A setter works like a security check 🛂: it inspects the value before letting it in.
Real example: temperature converter 🌡️
5. Object Copying
Objects are shared, not copied, when you assign them to another variable.
It's like sharing a Google Doc link: if your friend edits it, you see the change too, because there is only one document.
To get your own separate copy, use one of these:
Spread operator ... (most common ✅)
Object.assign()
Copying and changing at the same time
6. Shallow vs Deep Copying
This is where many developers get confused, so let's go slowly. 🐢
Shallow copy: only the top level is copied
Shallow copying** creates a new object, but nested objects or arrays are still shared with the original object.
{ ...obj } and Object.assign() make a shallow copy. The top-level properties are copied, but objects inside are still shared.
Think of photocopying a file folder 📁. The first page is copied, but it has a note saying "see the attached Aadhaar card." Both folders point to the same Aadhaar card!
Deep copy: everything is copied
Deep copying creates a completely independent copy, including all nested objects and arrays.
A deep copy copies every level, so nothing is shared. Use structuredClone().
The old trick: JSON.parse(JSON.stringify())
You may see this in older code. It works for simple data, but it loses functions, undefined, and dates.
Quick comparison
| Method | Type | Nested objects |
|---|---|---|
b = a | ❌ Not a copy | Everything is shared |
{ ...a } | Shallow copy | Nested objects are shared |
Object.assign({}, a) | Shallow copy | Nested objects are shared |
structuredClone(a) | ✅ Deep copy | Fully separate (functions not allowed) |
JSON.parse(JSON.stringify(a)) | Deep copy (old) | Loses functions, dates, undefined |
Simple rule: If your object has no nested objects, use
{ ...obj }. If it does, usestructuredClone(obj).
7. Object.keys, Object.values, and Object.entries
These three methods turn an object into an array, so you can use array tools like map, filter, reduce, and for...of.
| Method | Returns | Example result |
|---|---|---|
Object.keys(obj) | An array of keys | ["Maths", "Science"] |
Object.values(obj) | An array of values | [92, 88] |
Object.entries(obj) | An array of key, value pairs | [["Maths", 92], ...] |
Real examples
Going back: Object.fromEntries()
Object.fromEntries() turns key, value pairs back into an object. Combine it with entries() to transform objects.
8. Property Descriptors
Every property has some hidden settings 🔧 behind it, like the settings of an app on your phone. These settings are called property descriptors.
| Setting | Meaning | If false |
|---|---|---|
value | The actual data | — |
writable | Can the value be changed? | It becomes read-only |
enumerable | Does it show up in loops and Object.keys()? | It stays hidden |
configurable | Can the property be deleted or its settings changed? | It is locked |
For normal properties, all three are true.
Creating a read-only property
Use Object.defineProperty() to set these options yourself.
Hiding a property from loops
⚠️
defineProperty()sets everything tofalseby default, unless you say otherwise.
Locking a whole object
| Method | Add new? | Delete? | Change values? |
|---|---|---|---|
Object.preventExtensions(obj) | ❌ | ✅ | ✅ |
Object.seal(obj) | ❌ | ❌ | ✅ |
Object.freeze(obj) | ❌ | ❌ | ❌ |
9. Symbols
A Symbol is a special value that is always unique. No two symbols are ever the same, even if they have the same description.
Think of two people both named "Rahul Sharma" 👥. Same name, but each has a different Aadhaar number. A symbol is like that Aadhaar number: completely unique.
Using a symbol as an object key
Symbols are used as keys that can never clash with other keys.
Symbol keys are hidden from normal loops
Why are symbols useful?
Imagine you're using an object from someone else's code (a library), and you want to add your own property. If you use a normal key like id, you might overwrite their id by mistake! 😱
A symbol key never clashes:
As a beginner, you won't use symbols often. Just remember: symbols are unique, hidden keys. JavaScript also uses built-in symbols internally, like
Symbol.iterator, which makesfor...ofwork.
Common Mistakes to Avoid
| Mistake | Problem | Fix |
|---|---|---|
{ field: "Pune" } when the key is in a variable | Creates a key literally named "field" | Use { [field]: "Pune" } |
| Destructuring a key that doesn't exist | Gets undefined | Give a default: { city = "Delhi" } |
| Reading deep values without checking | ❌ TypeError | Use ?. |
Writing obj.fullName() for a getter | ❌ TypeError: not a function | Write obj.fullName |
Copying with b = a | Both share the same object | Use { ...a } or structuredClone(a) |
Using { ...a } for nested objects | Nested parts are still shared | Use structuredClone(a) |
Expecting symbol keys in Object.keys() | Symbols are hidden | Use Object.getOwnPropertySymbols() |
Quick Summary Table
| Topic | Example |
|---|---|
| Computed property | { [key]: value } |
| Destructuring | const { name, age } = user; |
| Rename + default | const { name: userName, city = "Delhi" } = user; |
| Optional chaining | user.address?.city ?? "N/A" |
| Getter | get fullName() { return ... } |
| Setter | set age(v) { if (v > 0) this._age = v; } |
| Shallow copy | { ...obj } |
| Deep copy | structuredClone(obj) |
| Keys / values / pairs | Object.keys(), Object.values(), Object.entries() |
| Pairs back to object | Object.fromEntries(pairs) |
| Read-only property | Object.defineProperty(obj, "x", { value: 1, writable: false }) |
| Unique key | const id = Symbol("id"); obj[id] = 1; |
Key Takeaways
- Computed properties
{ [key]: value }let you create keys from variables, and destructuringconst { a, b } = objunpacks properties by name. - Optional chaining
?.safely reads nested values that might be missing. Combine it with??for a default. - Getters run code when a property is read, and setters run code when it is changed, which is great for calculated values and validation.
{ ...obj }makes a shallow copy (nested objects are still shared). UsestructuredClone()for a deep copy.- Property descriptors control whether a property can be changed, looped over, or deleted, and symbols create unique, hidden keys that never clash.