Elementor Header #8

43. Неизменяемость

Неизменяемость данных — это концепция в программировании, согласно которой данные не изменяются после их создания. Вместо этого, при необходимости модификации данных создаются новые версии этих данных. Эта идея широко используется для предотвращения непредсказуемого поведения в программах, повышения их надёжности и облегчения отладки.

В JavaScript неизменяемость чаще всего касается работы с примитивными типами данных и объектами. Понимание того, когда данные изменяются, а когда создаются новые копии, важно для написания чистого и предсказуемого кода.

Неизменяемость примитивных типов данных

Примитивные типы данных в JavaScript (такие как number, string, boolean, null, undefined, symbol, bigint) неизменяемы по своей природе. Это значит, что после создания значение примитива не может быть изменено.

Пример:

				
					let a = 10;
let b = a; // b получает значение 10, но это копия, не ссылка

a = 20; // меняем значение a

console.log(a); // 20
console.log(b); // 10

				
			

В этом примере a и b — это разные переменные, которые хранят разные значения. Изменение значения переменной a не влияет на значение переменной b, так как числа (как и другие примитивы) неизменяемы.

Неизменяемость объектов

Объекты и массивы в JavaScript изменяемы по умолчанию. Это означает, что при изменении объекта или массива вы фактически изменяете саму структуру данных.

Пример:

				
					let person = {
  name: "John",
  age: 30
};

let anotherPerson = person; // anotherPerson ссылается на тот же объект

anotherPerson.age = 31; // изменение в anotherPerson также затронет person

console.log(person.age); // 31
console.log(anotherPerson.age); // 31

				
			

В этом примере обе переменные person и anotherPerson ссылаются на один и тот же объект в памяти. Любые изменения в одной переменной будут отражаться и в другой.

Создание неизменяемых объектов

Чтобы обеспечить неизменяемость объектов в JavaScript, разработчики часто используют несколько подходов:

Создание копий объектов: Вместо изменения существующего объекта можно создать его копию с необходимыми изменениями.

Использование метода Object.assign:

				
					let person = {
  name: "John",
  age: 30
};

let newPerson = Object.assign({}, person, { age: 31 });

console.log(person.age); // 30
console.log(newPerson.age); // 31

				
			

Использование оператора «spread»:

				
					let newPerson = { ...person, age: 31 };

				
			

Глубокое копирование объектов: Если объект содержит вложенные объекты, простое копирование (как в случае с Object.assign) не достаточно. Нужно создавать глубокие копии.

				
					let person = {
  name: "John",
  details: {
    age: 30,
    address: "123 Main St"
  }
};

let newPerson = JSON.parse(JSON.stringify(person));
newPerson.details.age = 31;

console.log(person.details.age); // 30
console.log(newPerson.details.age); // 31

				
			

Использование Object.freeze: Метод Object.freeze позволяет сделать объект полностью неизменяемым, т.е. любые попытки изменить его свойства будут игнорироваться.

				
					let person = {
  name: "John",
  age: 30
};

Object.freeze(person);

person.age = 31; // изменение не сработает
console.log(person.age); // 30

				
			

Библиотеки для работы с неизменяемыми данными: В JavaScript существуют библиотеки, такие как Immutable.js, которые упрощают работу с неизменяемыми структурами данных.

Преимущества неизменяемости

  • Безопасность и предсказуемость: Неизменяемые данные не могут быть случайно изменены в другом месте программы, что снижает вероятность ошибок.
  • Упрощенная отладка: Так как данные не изменяются, их проще отследить в процессе выполнения программы.
  • Оптимизация производительности: В некоторых случаях неизменяемость может позволить оптимизировать использование памяти и повысить производительность.

Недостатки неизменяемости

  • Необходимость создания новых объектов: При каждом изменении данных создается новый объект, что может привести к увеличению использования памяти.
  • Сложность работы с большими структурами данных: Создание копий сложных или больших объектов может быть ресурсоемким.

Распространенные ошибки

Поверхностное копирование вместо глубокого копирования: Часто разработчики пытаются создать копию объекта с помощью методов, которые копируют только верхний уровень свойств. Если в объекте есть вложенные объекты, то изменения в этих вложенных структурах отразятся на всех копиях.

Пример ошибки:

				
					let person = {
  name: "Alice",
  details: {
    age: 25
  }
};

let newPerson = Object.assign({}, person);
newPerson.details.age = 26;

console.log(person.details.age); // 26 — изменилось, хотя не должно было

				
			

Правильный подход — использовать глубокое копирование.

Попытка изменять неизменяемые объекты: Если объект был заморожен с помощью Object.freeze, любые попытки изменить его свойства будут просто игнорироваться. Часто разработчики не замечают, что объект заморожен, и удивляются, почему изменения не применяются.

Пример ошибки:

				
					let config = {
  apiUrl: "https://api.example.com"
};

Object.freeze(config);
config.apiUrl = "https://api.changed.com"; // изменение не произойдет

console.log(config.apiUrl); // "https://api.example.com"

				
			

Путаница между ссылкой и значением: Когда переменные ссылаются на один и тот же объект, изменение одной переменной изменяет и объект, связанный с другой переменной. Это часто приводит к непредсказуемым ошибкам.

Пример ошибки:

				
					let arr1 = [1, 2, 3];
let arr2 = arr1; // arr2 ссылается на тот же массив, что и arr1

arr2.push(4);

console.log(arr1); // [1, 2, 3, 4]

				
			

Чтобы избежать этого, используйте методы, которые создают новые копии данных, такие как slice для массивов или spread для объектов.

Заключение

Неизменяемость данных — это важный принцип в программировании, который помогает создавать более надежные и устойчивые к ошибкам программы. В JavaScript неизменяемость особенно актуальна при работе с объектами и массивами, поскольку они изменяемы по умолчанию. Понимание того, как работать с неизменяемыми данными, позволяет вам писать более безопасный и предсказуемый код.

Тестовое задание

  1. Создайте объект user с двумя свойствами: name и details (где details — это объект с полями age и city).
  2. Создайте функцию, которая принимает объект user и возвращает новый объект с обновленным возрастом, не изменяя исходный объект.
  3. Используйте метод Object.freeze для создания неизменяемого объекта и попытайтесь изменить одно из его свойств.

Пример решения:

				
					// Задание 1
let user = {
  name: "Alice",
  details: {
    age: 25,
    city: "New York"
  }
};

// Задание 2
function updateUserAge(user, newAge) {
  return {
    ...user,
    details: {
      ...user.details,
      age: newAge
    }
  };
}

let updatedUser = updateUserAge(user, 26);

console.log(user.details.age); // 25
console.log(updatedUser.details.age); // 26

// Задание 3
let frozenUser = Object.freeze({ name: "Bob", age: 40 });

frozenUser.age = 41; // изменение не сработает
console.log(frozenUser.age); // 40

				
			

Этот урок дал вам представление о неизменяемости данных в JavaScript, о том, как её достичь и какие преимущества она может предоставить. Понимание и применение этой концепции поможет вам создавать более качественный и надёжный код.

logo