When Your Object Has a Mind of Its Own
Have you ever faced a weird moment while coding?
You created an object in JavaScript, set the values, but suddenly — boom! — something changed. You didn’t touch it (at least, not that you remember), but somehow the data's different.
let user = { name: "John" };
let copy = user;
copy.name = "Alice";
console.log(user.name); // "Alice" ???
Yep, both user
and copy
point to the same object!
Why Does This Happen?
In JavaScript, objects (including arrays and functions) are reference types.
This means when you assign an object to a variable, you're assigning a reference to it — not a copy of the object.
It’s like giving someone your house address. If they repaint your house, you’ll see the new color too. Because it’s still the same house.
let user = { name: "John" };
let clone = user;
clone.name = "Mark";
console.log(user.name); // Mark
Primitive vs Reference Types
Primitive | Reference |
---|---|
Number, String, Boolean, Null, Undefined, Symbol, BigInt | Object, Array, Function |
// Primitive
let a = 10;
let b = a;
b = 20;
console.log(a); // 10
// Reference
let obj1 = { num: 10 };
let obj2 = obj1;
obj2.num = 99;
console.log(obj1.num); // 99
Why It’s Dangerous
- You might unintentionally modify original data
- Changes in one place can affect others
- State becomes hard to track
- Can cause bugs in frameworks like React/Vue
How to Avoid Unexpected Mutations
1. Use Shallow Copy
Use Object.assign()
or the spread operator ...
for shallow copies:
let user = { name: "Jane", age: 25 };
let copy = { ...user };
copy.name = "Lucy";
console.log(user.name); // Jane
But beware of nested objects:
let user = {
name: "Alex",
address: { city: "Jakarta" }
};
let copy = { ...user };
copy.address.city = "Bandung";
console.log(user.address.city); // Bandung
Because nested properties are still references.
2. Deep Copy
let deepCopy = JSON.parse(JSON.stringify(user));
This creates a new object entirely — but has limitations:
- Doesn't work with functions, Dates, RegExps
- Fails with circular references
3. Use Libraries for Deep Clone
Try tools like:
- Lodash →
_.cloneDeep(obj)
- Ramda
- Immer
import _ from 'lodash';
let copy = _.cloneDeep(user);
4. Freeze the Object
Prevent mutations using Object.freeze()
:
let user = Object.freeze({
name: "Budi"
});
user.name = "Joko"; // silently ignored
console.log(user.name); // Budi
Note: Freezing is shallow — nested objects are still mutable.
5. Use Immutable Patterns
let newUser = {
...user,
name: "Dina"
};
This is standard in modern frameworks like React or Redux.
Avoid Direct Mutation
// Bad
function updateUser(user) {
user.name = "Ani";
return user;
}
// Better
function updateUser(user) {
return {
...user,
name: "Ani"
};
}
Common Bug Examples
1. React Component Doesn’t Re-render
state.user.name = "Joko"; // modifies state directly
// React doesn’t notice the change
Fix:
setState({ user: { ...state.user, name: "Joko" } });
2. API Data Modified Unexpectedly
const data = await fetchData();
let copy = data;
copy.items[0].name = "X";
console.log(data.items[0].name); // "X"
Fix:
let safeCopy = JSON.parse(JSON.stringify(data));
Or:
let safeCopy = _.cloneDeep(data);
Best Practices Checklist
- Use
{ ...obj }
orObject.assign()
for shallow copies - Use
_.cloneDeep()
or JSON deep copy for full clones - Never mutate props/state/API data directly
- Use immutable update patterns
- Remember arrays are also objects!
Objects Aren’t Evil — Just Misunderstood
JavaScript objects are powerful — but if you treat them like primitives, you're gonna have a bad time.
Understanding reference behavior and making safe copies will save you from mysterious bugs, especially in complex apps.
“Respect the reference. Copy wisely. And freeze when necessary.”
No comments:
Post a Comment