JavaScript Immutability

By default in JavaScript, variables are passed by reference. This can present some strange results. Before we look at an example, let me explain the basic concept of Mutability vs Immutability.

Mutability vs Immutability

Mutability simple means the ability to change. And immutability is obviously the opposite of that, not able to change.

In JavaScript primitive types (strings, numbers, bools, etc) are immutable. Reference types (objects, arrays and functions) are mutable.

If you’ve read about Javascript Variables you may think that the introduction of the const keyword in ES6 will allow us to create an immutable variable. But that’s not the case. All the const keyword allows is for a read-only variable who’s value cannot be replaced. If an object is assigned to a const variable, it’s properties can still change.

Let’s look at an example of where this becomes a problem.

const vehicle = {
	make: 'Toyota',
	model: 'Tacoma',
	speed: 120
};

const newSpeed = (vehicle, speed) => {
	vehicle.speed = speed;
	return vehicle;
};

const newVehicle = newSpeed(vehicle, 100);

console.log(vehicle);
console.log(newVehicle);

// Both of the above will output the same object:
// {
//  make: "Toyota",
//  model: "Tacoma",
//  speed: 100
//}

The reason the above outputs the same object is that vehicle is passed by reference. We’re returning a reference of vehicle in to newVehicle and so they’re both pointing to the same object. Be careful of this!

Immutable JavaScript objects

So how do we get around this? We need to create a copy. And what’s the easiest way to copy objects? Use the spread operator.

Let’s modify the above example to work as we originally intended.

const vehicle = {
	make: 'Toyota',
	model: 'Tacoma',
	speed: 120
};

const newSpeed = (vehicle, speed) => {
	return {
		...vehicle,
		speed: speed
	};
};

const newVehicle = newSpeed(vehicle, 100);

console.log(vehicle);
console.log(newVehicle);

// The above will output:
// {
//   make: "Toyota",
//   model: "Tacoma",
//   speed: 120
// }
// {
//   make: "Toyota",
//   model: "Tacoma",
//   speed: 100
// }

The only thing to be further aware of here is that if an object has a property which is also a reference type (object, array, function), those too will be passed by reference in the new object.

const vehicle = {
	make: 'Toyota',
	model: 'Tacoma',
	colors: ['white', 'red', 'blue']
};

const createNewVehicle = vehicle => {
	return {
		...vehicle
	};
};

let newVehicle = createNewVehicle(vehicle);
newVehicle.colors.push('green');

console.log(vehicle);
console.log(newVehicle);

// Both the above will output:
// {
//   colors: ["white", "red", "blue", "green"],
//   make: "Toyota",
//   model: "Tacoma"
// }

To fix this, we need to spread the properties as well.

const vehicle = {
	make: 'Toyota',
	model: 'Tacoma',
	colors: ['white', 'red', 'blue']
};

const createNewVehicle = vehicle => {
	return {
		...vehicle,
		colors: [...vehicle.colors]
	};
};

let newVehicle = createNewVehicle(vehicle);
newVehicle.colors.push('green');

console.log(vehicle);
console.log(newVehicle);

// The above will output:
// {
//   colors: ["white", "red", "blue"],
//   make: "Toyota",
//   model: "Tacoma"
// }
// {
//   colors: ["white", "red", "blue", "green"],
//   make: "Toyota",
//   model: "Tacoma"
// }

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *