内容简介:TheThe basic definition ofThe first thing to notice is that all
The immutability is quite a buzzword lately. Today we attempt to find out why. In this article, we explain what the immutability is and how we can benefit from it. When defining immutability, we look into various aspects of it. We investigate primitive values, assignment immutability, and how to keep our data structures immutable. We also look into how we can achieve immutability using built-in features.
Defining immutability
The basic definition of immutability is to be unable to change. It is one of the core principles of functional programming . Even if we don’t aim to write code that fully follows the rules of functional programming, we can profit from diving into it.
Types immutability
The first thing to notice is that all primitive values are in their nature immutable. We can distinguish seven primitive data types: number, bigint, boolean, string, null, undefined, and symbol.
We sometimes perceive strings as arrays. That suggests that we might mutate them, but this is not the case:
const name = 'John Smith'; name[0] = 'j'; console.log(name); // 'John Smith';
The interesting thing is that JavaScript wraps primitive types in objects. An example of such is the String . We can observe this in the below example:
console.log(typeof name); // 'string' String.prototype.getType = function () { return typeof this; }; console.log(name.getType()); // 'object'
The wrappers are mutable, but they disappear after they are not needed anymore. Every time we access a property on a primitive value, we have a new wrapper.
The above behavior is sometimes referred to as boxing
The primitive values being immutable is the reason why all methods that operate on primitives return new values instead of mutating them.
A good example is String.prototype.toUpperCase()
Assignment immutability
It is important not to confuse value immutability with the assignment immutability .
let hello = 'Hello'; hello += ' World!';
Above, we don’t mutate the string. Instead, we assign a new value to the hello variable that is a concatenation of two strings.
The above assignment would fail if we were to use the const keyword. Let’s inspect the example below:
const user = { name: 'Smith' }; user.name = 'John';
We can successfully mutate the object that the user variable holds, even though we used const to define it. This is because it only ensures the assignment immutability .
On the other hand, when using TypeScript, we can use a const assertion .
const user = { name: 'Smith' } as const;
Doing the above marks all of the properties of the above object as readonly .
If you want to know more, check out TypeScript type inference with const assertions and the infer keyword
Reasons to worry about value mutability
First, let’s look into why we might consider keeping our values immutable .
Keeping our structures immutable makes any changes to the data more apparent. Doing so makes our code easier to read.
function doubleTheNumbers(arrayOfNumbers) { for(let i = 0; i < arrayOfNumbers.length; ++i) { arrayOfNumbers[i] += arrayOfNumbers[i]; } }
const numbers = [1, 2, 3]; doubleTheNumbers(numbers); console.log(numbers); // [2, 4, 6]
The doubleTheNumbers function doubles all of the provided numbers.
Right after writing all of the above, we know precisely how it works. Unfortunately, if the code is a lot longer and we come back to it after a few months, we might lose track of how we mutate our array. Also, such code is not very readable for other teammates.
function getDoubledNumbers(arrayOfNumbers) { return arrayOfNumbers.map(number => number * 2); }
const numbers = [1, 2, 3]; const doubledNumbers = getDoubledNumbers(numbers); console.log(numbers); // [1, 2, 3]; console.log(doubledNumbers); // [2, 4, 6];
Our getDoubledNumbers function returns a new array instead of modifying a new one. Since we now don’t perform a side effect , we can consider the function pure .
If you want to know more about pure functions and side effects, check out Improving our code with pure functions
Also, notice that the name of the function differs. Calling it getDoubledNumbers suggests that we now get a new array, instead of modifying the old one.
JavaScript has a lot of methods that operate on data without mutating it.
const doubleTheNumber = number => number * 2;
const doubledNumbers = numbers.map(doubleTheNumber);
The above approach really shines in terms of readability when chaining multiple operations.
Putting JavaScript aside for a second, immutability might prove to be useful when avoiding race conditions while performing multi-threaded operations.
On the other hand, Node.js makes an attempt of creating memory shared between threads using a SharedArrayBuffer . If you want to know more, check out Node.js TypeScript #13. Sending data between Worker Threads
Summing up, making changes to our data in an apparent way helps us avoid trouble. Also, avoiding mutating data makes our codebase easier to test.
Achieving immutability using built-in features
The first thing that comes into mind when attempting to achieve immutability is Object.freeze .
Object frozen using the above function can no longer be changed. It means that we can’t add and remove properties, or change them in any way.
const user = Object.freeze({ name: 'Smith' });
user.name = 'John';
The above attempt to mutate the value fails silently or results in throwing an error if we are in a strict mode.
An interesting thought is that the Object . freeze mutates the data in a particular way. Instead of returning a new instance, it freezes the provided object.
Also, a very important thing is that the immutability we achieve above is shallow .
const user = Object.freeze({ name: 'Smith', address: { street: 'Sesame Street', city: 'New York' } });
user.address.city = 'Los Angeles'; console.log(user.address.city); // Los Angeles
If we would like to achieve deep immutability with Object.freeze , we would need to traverse the whole structure manually in a loop.
Object.seal
The Object.seal function is also somewhat associated with immutability. It does provide it to a lesser extent, though.
The above function seals an object by preventing us from adding or removing properties. We can still change existing properties.
const user = Object.seal({ firstName: 'John' });
user.firstName = 'James'; // works without issues user.lastName = 'Smith'; // fails
Summary
The immutability of values is not about not changing them at all. It is more about tracking those changes in a more structured, dependable manner and therefore being more confident in our code. While there are some libraries and native features that can help us in ensuring immutability, we might just be better off treating our data structures as immutable.
All of the advantages mentioned in the above article seem worth pursuing. Unfortunately, declaring new values instead of mutating existing ones might impact performance. We need to consider multiple things. For example, if you create said structures just a few times throughout the life of your application, the impact almost certainly is not a concern. If you do it very frequently, you might want to reconsider your approach if you jump into performance issues. You might also want to look into libraries like Immutable.js .
Approaching our code with avoiding the mutation of data might be very useful, but we need to consider the context of our application. Just as other disciplines such as Test-driven Development, static type analysis, and Object-Oriented Programming, it has its uses. We, as the programmers, are responsible for making the decision based on the pros and cons.
以上所述就是小编给大家介绍的《What's the deal with immutability in JavaScript?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。