Неизменяемость является основным принципом функционального программирования и многое может предложить также объектно-ориентированным программам. Изменяемый объект — это объект, состояние которого может быть изменено после его создания. Неизменяемый объект — это объект, состояние которого не может быть изменено после его создания.
Примеры неизменяемых объектов в JavaScript?
В JavaScript некоторые встроенные типы (числа, строки) являются неизменяемыми, но пользовательские объекты, как правило, изменяемы. Некоторые встроенные неизменяемые объекты JavaScript — это Math
, Date
.
Вот несколько способов добавить/смоделировать неизменяемость для обычных объектов JavaScript.
Константные свойства объекта
Комбинируя writable: false
и configurable: false
, вы можете, по сути, создать константу (которую нельзя изменить, переопределить или удалить) как свойство объекта, например:
let myObject = {};
Object.defineProperty(myObject, 'number', {
value: 42,
writable: false,
configurable: false,
});
console.log(myObject.number); // 42
myObject.number = 43;
console.log(myObject.number); // 42
Предотвращение расширений
Если вы хотите запретить добавление новых свойств к объекту, но при этом оставить остальные свойства объекта без изменений, вызовите Object.preventExtensions(...)
:
var myObject = {
a: 2,
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
В нестрогом режиме создание b
завершается без ошибок. В строгом режиме оно выбрасывает TypeError
.
Запечатывание
Object.seal()
создает «запечатанный» объект, что означает, что он берет существующий объект и, по сути, вызывает на нем Object.preventExtensions()
, а также помечает все его существующие свойства как configurable: false
.
Таким образом, вы не только не можете добавлять новые свойства, но также не можете перенастраивать или удалять существующие свойства (хотя вы все еще можете изменять их значения).
Замораживание
Object.freeze()
создает замороженный объект, что означает, что он берет существующий объект и, по сути, вызывает на нем Object.seal()
, а также помечает все свойства «аксессора данных» как writable:false
, чтобы их значения не могли быть изменены.
Этот подход является наивысшим уровнем неизменяемости, которого вы можете достичь для самого объекта, поскольку он предотвращает любые изменения объекта или любого из его прямых свойств (хотя, как упоминалось выше, содержимое любых ссылающихся на другие объекты не затрагивается).
var immutable = Object.freeze({});
Замораживание объекта не позволяет добавлять новые свойства к объекту и предотвращает удаление или изменение существующих свойств. Object.freeze()
сохраняет перечисляемость, конфигурируемость, записываемость и прототип объекта. Он возвращает переданный объект и не создает замороженную копию.
Каковы плюсы и минусы неизменяемости?
Плюсы
- Более простое обнаружение изменений — равенство объектов может быть определено производительным и простым способом через ссылочное равенство. Это полезно для сравнения различий объектов в React и Redux.
- Программы с неизменяемыми объектами проще для понимания, поскольку вам не нужно беспокоиться о том, как объект может развиваться с течением времени.
- Защитные копии больше не нужны, когда неизменяемые объекты возвращаются из функций или передаются в них, поскольку нет возможности их изменения.
- Простое совместное использование через ссылки — одна копия объекта так же хороша, как и другая, поэтому вы можете кэшировать объекты или повторно использовать один и тот же объект несколько раз.
- Потокобезопасность — неизменяемые объекты могут безопасно использоваться между потоками в многопоточной среде, поскольку нет риска их изменения в других одновременно выполняющихся потоках.
- При использовании таких библиотек, как ImmutableJS, объекты изменяются с использованием структурного совместного использования, и для хранения нескольких объектов со схожими структурами требуется меньше памяти.
Минусы
- Наивные реализации неизменяемых структур данных и операций с ними могут привести к чрезвычайно низкой производительности, поскольку каждый раз создаются новые объекты. Рекомендуется использовать библиотеки для эффективных неизменяемых структур данных и операций, использующих структурное совместное использование.
- Выделение (и освобождение) множества мелких объектов вместо изменения существующих может вызвать снижение производительности. Сложность аллокатора или сборщика мусора обычно зависит от количества объектов в куче.
- Циклические структуры данных, такие как графы, трудно построить. Если у вас есть два объекта, которые не могут быть изменены после инициализации, как вы можете заставить их указывать друг на друга?
Как вы можете добиться неизменяемости в своем коде?
Альтернатива — использовать объявления const
в сочетании с упомянутыми выше методами создания. Для «мутирования» объектов используйте оператор распространения, Object.assign
, Array.concat()
и т. д., чтобы создавать новые объекты вместо того, чтобы мутировать исходный объект.
Примеры:
// Пример массива
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]
// Пример объекта
const human = Object.freeze({ race: 'human' });
const john = { ...human, name: 'John' }; // {race: "human", name: "John"}
const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}