Концепция неизменяемости не является чем-то новым, поскольку она давно существует в функциональных и объектно-ориентированных языках программирования. Идея не очень распространена в JavaScript, но в последнее время она постепенно переросла в сообщество программистов. React является активным сторонником сохранения неизменности состояния, и Facebook предоставил библиотеку Immutable.js, чтобы помочь в этом деле.
В этом уроке мы ответим на следующие вопросы:
- Что такое неизменность?
- Почему и когда вы должны сохранять свой штат неизменным?
- Проблемы, связанные с мутациями
Изменяемые и неизменяемые типы данных
Изменчивый объект — это объект, состояние которого может быть изменено или изменено с течением времени. С другой стороны, неизменный объект — это объект, состояние которого невозможно изменить после его создания. Ну, вот как учебник определяет изменяемые и неизменяемые объекты.
В JavaScript строка и числа являются неизменяемыми типами данных. Если это кажется странным, вот пример, демонстрирующий, почему мы называем их неизменными.
Джава
1
var x =7;
2
x += 1;
Например, числа неизменны, потому что вы не можете изменить их значение. Например, вы не можете буквально изменить значение от 7 до 8. Это не имеет смысла. Вместо этого вы можете изменить значение, хранящееся в переменной x, с 7 на 8.
Строки также являются неизменяемыми, и это очевидно, когда вы пытаетесь изменить определенный символ в строке.
Джава
xxxxxxxxxx
1
var myString = "I am immutable"
2
myString[2] = 'c'
3
console.log(myString)
Но это не мешает вам создавать новые строки.
Джава
xxxxxxxxxx
1
var myString = "I am immutable.";
2
var newString = myString.slice(0,7);
Другие методы манипуляции со строками, такие как trim
, concat
и т. Д., Также возвращают новую строку. Никакие строковые операции не изменяют строку, с которой они работают в JavaScript.
Это потому, что строки и числа являются примитивными типами значений, которые назначаются по значению, а не по ссылке. Вот хорошее обсуждение копирования по значению против копирования по ссылке для начинающих.
Вам также может понравиться: Вам действительно нужны неизменные данные?
Практические соображения
Когда я работал над своим продуктом Storylens — бесплатной блог-платформой , я использовал комбинацию React и Redux. И React, и Redux уделяют большое внимание проверке на неглубокое равенство. Это гарантирует, что DOM перерисовывается только тогда, когда ссылка на базовый объект изменилась. Поэтому, когда вы обновляете состояние или хранилище в React / Redux, вам нужно избегать мутирования объектов и массивов.
Точно так же большинство современных стеков внешнего интерфейса сегодня сильно зависят от неизменных структур данных, и, если вы собираетесь их использовать, возможно, было бы лучше привыкнуть к этим концепциям. Давайте посмотрим на массивы и объекты.
Массивы и объекты изменчивы
Массивы и объекты не являются неизменяемыми в JavaScript, поскольку они действительно могут со временем изменять свои значения. Давайте проверим это, запустив следующие примеры кода в консоли.
Джава
xxxxxxxxxx
1
var x = {
2
foo: 'bar'
3
};
4
var y = x;
5
x.foo = 'Something else';
6
console.log(y.foo); // Something else
7
console.log(x === y) // true
Как видите, x является изменяемым объектом, и любое изменение свойства x отражается в значении y. Почему? Все они имеют одну и ту же ссылку. Поэтому, когда вы обновляете значение свойства x, вы изменяете значение для всех ссылок на этот объект.
Давайте посмотрим на массивы:
Джава
xxxxxxxxxx
1
var x = ['foo'];
2
var y = x.push('bar')
Несомненно, что значение y будет содержать как ‘foo’, так и ‘bar’. Но как насчет х?
Джава
xxxxxxxxxx
1
console.log(x); // ['foo', 'bar']
2
console.log(x === y) // true
И x, и y являются ссылками на один и тот же элемент, а метод push изменяет исходный массив. Многие методы массивов, которые вы используете каждый день, являются методами Mutator. Вот список методов Array, которые работают с исходным массивом.
- copyWithin
- заливка
- поп
- От себя
- обратный
- сдвиг
- Сортировать
- сращивание
- unshift
Такие методы, как map и filter, являются неизменными, поскольку они создают новый массив без изменения исходного массива. Вы можете убедиться в этом, запустив такой пример:
Джава
xxxxxxxxxx
1
let a = [1,2,3,4,5]
2
let b = a.map(item => item-1)
3
console.log(a ==b) //false
4
console.log(a) //[1,2,3,4,5]
Итак, что означает сделать объекты и массивы неизменными? Что ж, в JavaScript есть методы, которые вы можете использовать для создания новых значений без потери предыдущих версий. Всякий раз, когда необходимо изменить содержимое объекта, вы не будете напрямую изменять исходный объект значения. Вместо этого мы будем рассматривать его как неизменяемый и возвращать совершенно новый объект с обновленным значением.
Неизменность в JavaScript
У вас есть много вариантов для обеспечения неизменности в JavaScript. Некоторые функции ES6 можно использовать для клонирования значений без использования исходных ссылок. В этом разделе мы рассмотрим различные методы реализации неизменяемости в JavaScript для массивов и объектов.
Объекты
Чтобы избежать мутации, опция, доступная в ES5, замораживала объект с помощью Object.freeze. Это был не очень хороший вариант. С ES6 вы можете предотвратить мутирование объектов, используя Object.assign
метод.
Object.assign () копирует значения (всех перечисляемых собственных свойств) из одного или нескольких исходных объектов в целевой объект. Он имеет подпись Object.assign (цель, … источники).
Давайте рассмотрим пример объектов:
Джава
xxxxxxxxxx
1
const person = {
2
name: 'Jim',
3
age: 19
4
}
5
const newPerson = Object.assign({}, person, {
6
age: 22
7
})
8
console.log(newPerson === person) // false
9
console.log(person) // { name: 'Jim', age: 19 }
10
console.log(newPerson) // { name: 'Jim', age: 22 }
Параметр Object.assign()
метода является целевым объектом, и вы можете передать в качестве источников более одного объекта. Метод объединяет все источники справа налево в указанном порядке и возвращает целевой объект. Вы можете использовать метод для объединения объектов и поверхностного их клонирования.
Учитывая довольно многословный синтаксис, Object.assign может затруднить чтение вашего кода. Существует альтернативный синтаксис, называемый оператором распространения объекта, который использует три точки для мелкого клонирования объекта. Оператор распространения позволяет вам копировать перечислимые свойства из нескольких источников, используя лаконичный способ. Вот как вы клонируете объекты, используя оператор распространения.
Джава
xxxxxxxxxx
1
const person = {
2
name: 'Jim',
3
age: 19
4
}
5
const newPerson = {
6
...person,
7
age: 22
8
}
9
console.log(newPerson === person) // false
10
console.log(newPerson) // { name: 'Jim', age: 22 }
Удаление свойств объекта
Существует несколько способов удаления свойства без изменения объекта. Мой личный фаворит — использование синтаксиса деструктурирования объектов.
Синтаксис деструктурирующего присваивания является выражением JavaScript, которое позволяет распаковывать значения из массивов или свойства объектов в отдельные переменные.
Вот пример:
Джава
xxxxxxxxxx
1
const x = {
2
foo: 'bar',
3
far: 'boo',
4
faz: 'baz'
5
};
Если вы уже знаете название свойства, которое хотите удалить, вы можете попробовать это: ‘
Джава
xxxxxxxxxx
1
const { foo, ...everythingElse } = x;
2
console.log(everythingElse);
3
// Will be { "far": "boo", "faz":"baz" }
Если имя удаляемого свойства является динамическим, вы можете сделать это:
Джава
xxxxxxxxxx
1
const key = 'far';
2
const { [key]: value, ...everythingElse} = x;
3
console.log(everythingElse);
4
// Will be { "foo": "bar", "faz":"baz" }
Массивы
Как упоминалось ранее, большинство методов массива являются изменяемыми. Но вы можете использовать синтаксис оператора распространения для массивов, чтобы добавлять и удалять элементы из массива. Давайте посмотрим на пример:
Джава
xxxxxxxxxx
1
const fruits = ['peach', 'pear', 'apple', 'plum']
2
const newFruits = [...fruits, 'orange']
3
console.log(fruits === newFruits) //false
Хорошо, это было легко! Мы сохранили старый массив без изменений и создали новый. Как насчет удаления?
splice()
Метод удаляет элемент из исходного массива. Таким образом, вы могли бы вместо этого использовать, filter()
который возвращает новый массив.
Неизменность в массиве объектов и вложенных объектов
Функции массива, такие как .map, являются неизменными, когда вы отображаете массив значений. Однако они не являются неизменными, когда вы работаете с массивом объектов. Давайте расширим пример фруктов, который мы создали ранее.
Джава
xxxxxxxxxx
1
fruits= [
2
{
3
id: 1,
4
name: "Peach"
5
},
6
{
7
id: 2,
8
name: "Pear"
9
},
10
{
11
id:3,
12
name: "Apple"
13
}
14
]
15
16
const updatedFruits = fruits.map(
17
item => {
18
item.name = "Orange";
19
return item
20
}
21
)
Джава
xxxxxxxxxx
1
console.log(fruits === updatedFruits) // True
Почему данные исходного массива также видоизменяются? Метод map клонирует исходный массив, как и ожидалось, но, поскольку мы работаем с массивом объектов, каждый элемент в массиве является ссылкой на объект в памяти. Любое изменение, которое вы вносите в объект в клонированном массиве, будет изменять исходный объект посредством ссылки.
Почему неизменность важна?
Если вы активно используете библиотеки внешнего интерфейса, такие как React, Vue, или библиотеки управления состоянием, такие как Redux, возможно, вы столкнулись с предупреждениями о том, что вам не следует изменять состояние. Сохранение состояния неизменным может помочь вам с точки зрения производительности, предсказуемости и лучшего отслеживания мутаций.
Предсказуемость
Для любого приложения среднего размера будет состояние — его много — и асинхронные действия обновляют это состояние. Состояние приложения будет значительно отличаться от исходного состояния после того, как конечный пользователь начнет его использовать. Мутация скрывает изменения, которые приводят к побочным эффектам, что, в свою очередь, затрудняет отладку и поиск ошибок. Сохраняя неизменность своих структур, вы сможете предсказать, что находится в состоянии в любой момент времени, и вы можете быть уверены, что не будет никаких неприятных побочных эффектов.
Отслеживание мутаций
С неизменяемыми объектами вы можете видеть изменения, которые происходят с этими объектами, как цепочку событий. Это потому, что переменные имеют новые ссылки, которые легче отслеживать по сравнению с существующими переменными. Это может особенно помочь вам в отладке кода и создании лучших параллельных приложений. Существуют отладчики событий, которые помогают воспроизводить события DOM с воспроизведением видео, которое полностью основано на отслеживании мутаций.
Резюме
Итак, в этой статье мы рассмотрели основы мутаций и почему слово неизменность так популярно в мире JavaScript в последнее время. Если у вас есть какие-либо вопросы, дайте мне знать в комментариях.