Разработка сайтов в Луганске, ЛНР. Руководство по присвоению переменных и мутациям в JavaScript

Мутации — это то, о чем вы довольно часто слышите в мире JavaScript, но что это такое и так ли они злы, как их изображают?

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

Если вы хотите изучить эту тему более подробно или освоить современный JavaScript, ознакомьтесь с первой главой моей новой книги Learn to Code with JavaScript бесплатно.

Начнем с того, что вернемся к самым основам типов значений...

Типы данных

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

числа, такие как 3, 0, -4,0.625

строки, такие как 'Hello’, «World», `Hi`,''

Булевы значения trueиfalse

null

undefined

символы — уникальный токен, который гарантированно никогда не столкнется с другим символом.

BigInt— для работы с большими целыми значениями

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

Назначение переменной

Присвоение переменной — это одна из первых вещей, которую вы изучаете в программировании. Например, вот так мы бы присвоили номер 3переменной bears:

const bears = 3;

Распространенной метафорой переменных является один из ящиков с метками, внутри которых находятся значения. Приведенный выше пример будет представлен в виде коробки с надписью «медведи» со значением 3 внутри.

переменные как коробка

Альтернативный способ думать о том, что происходит, — это ссылка, которая сопоставляет метку bearsсо значением 3:

переменные как ссылка

Если я назначу число 3другой переменной, оно будет ссылаться на то же значение, что и медведи:

let musketeers = 3;

переменные, ссылающиеся на одно и то же значение

Переменные bearsи musketeersобе ссылаются на одно и то же примитивное значение 3. Мы можем проверить это, используя оператор строгого равенства ===:

bears === musketeers

<< true

Оператор равенства возвращает trueзначение, если обе переменные ссылаются на одно и то же значение.

Некоторые подводные камни при работе с объектами

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

const ghostbusters = { number: 4 };

Это присваивание означает, что переменная ghostbustersссылается на объект:

переменные, ссылающиеся на разные объекты

Однако большая разница при назначении объектов переменным заключается в том, что если вы назначаете другой объектный литерал другой переменной, он будет ссылаться на совершенно другой объект — даже если оба объектных литерала выглядят совершенно одинаково! Например, присваивание ниже выглядит так, будто переменная tmnt (Черепашки-ниндзя) ссылается на тот же объект, что и переменная ghostbusters:

let tmnt = { number: 4 };

Несмотря на то, что переменные ghostbustersи tmntвыглядят так, как будто они ссылаются на один и тот же объект, на самом деле они ссылаются на совершенно разные объекты, в чем мы можем убедиться, проверив оператор строгого равенства:

ghostbusters === tmnt

<< false

переменные, ссылающиеся на разные объекты

Переназначение переменной

Когда constключевое слово было введено в ES6, многие люди ошибочно полагали, что константы были введены в JavaScript, но это было не так. Название этого ключевого слова немного вводит в заблуждение.

Любая переменная, объявленная с constпомощью, не может быть переназначена другому значению. Это касается примитивных значений и объектов. Например, переменная bearsбыла объявлена ​​с использованием constв предыдущем разделе, поэтому ей не может быть присвоено другое значение. Если мы попытаемся присвоить переменной число 2 bears, то получим ошибку:

bears = 2;

<< TypeError: Attempted to assign to readonly property.

Ссылка на число 3 фиксирована и bearsпеременной нельзя переназначить другое значение.

То же самое относится и к объектам. Если мы попытаемся присвоить переменной другой объект ghostbusters, мы получим ту же ошибку:

ghostbusters = {number: 5};

TypeError: Attempted to assign to readonly property.

Переназначение переменной с помощьюlet

Когда ключевое слово letиспользуется для объявления переменной, его можно переназначить для ссылки на другое значение позже в нашем коде. Например, мы объявили переменную musketeersс помощью let, поэтому мы можем изменить значение, на которое musketeersона ссылается. Если бы д’Артаньян присоединился к мушкетерам, их число увеличилось бы до 4:

musketeers = 4;

переменные, ссылающиеся на разные значения

Это можно сделать, потому что letбыл использован для объявления переменной. Мы можем изменить значение, на которое musketeersссылается, столько раз, сколько захотим.

Переменная tmntтакже была объявлена ​​с помощью let, поэтому ее также можно переназначить для ссылки на другой объект (или совершенно другой тип, если хотите):

tmnt = {number: 5};

Обратите внимание, что переменная tmntтеперь ссылается на совершенно другой объект; мы не просто изменили numberсвойство на 5.

Таким образом, если вы объявляете переменную с помощью const, ее значение не может быть переназначено и всегда будет ссылаться на одно и то же примитивное значение или объект, которому оно было первоначально присвоено. Если вы объявляете переменную с помощью let, ее значение может быть переназначено столько раз, сколько потребуется позже в программе.

Использование constкак можно чаще считается хорошей практикой, так как это означает, что значение переменных остается постоянным, а код более последовательным и предсказуемым, что делает его менее подверженным ошибкам и ошибкам.

Присвоение переменной по ссылке

В нативном JavaScript вы можете только присваивать значения переменным. Вы не можете назначать переменные для ссылки на другую переменную, даже если кажется, что можете. Например, количество марионеток равно количеству мушкетеров, поэтому мы можем присвоить переменной stoogesссылку на то же значение, что и переменная, musketeersиспользуя следующее:

const stooges = musketeers;

Похоже, что переменная stoogesссылается на переменную musketeers, как показано на диаграмме ниже:

переменные не могут ссылаться на другую переменную

Однако в нативном JavaScript это невозможно: переменная может ссылаться только на фактическое значение; он не может ссылаться на другую переменную. Что на самом деле происходит, когда вы выполняете такое присваивание, так это то, что переменная слева от присваивания будет ссылаться на значение, на которое ссылается переменная справа, поэтому переменная stoogesбудет ссылаться на то же значение, что и musketeersпеременная, то есть число 3. Однажды это присвоение было выполнено, stoogesпеременная вообще не связана с musketeersпеременной.

переменные, ссылающиеся на значения

Это означает, что если д’Артаньян присоединится к мушкетерам и мы установим значение musketeers4, значение stoogesостанется равным 3. На самом деле, поскольку мы объявили stoogesпеременную с помощью const, мы не можем установить для нее какое-либо новое значение; всегда будет 3.

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

Мутации

Значение называется изменяемым, если оно может быть изменено. Вот и все: мутация — это действие по изменению свойств значения.

Все примитивные значения в JavaScript неизменяемы: вы не можете изменить их свойства — никогда. Например, если мы присвоим строку «cake»переменной food, мы увидим, что мы не можем изменить ни одно из ее свойств:

const food = «cake»;

Если мы попытаемся изменить первую букву на «f», она выглядит так, как будто она изменилась:

food[0] = «f»;

<< «f»

Но если мы посмотрим на значение переменной, то увидим, что на самом деле ничего не изменилось:

food

<< «cake»

То же самое произойдет, если мы попытаемся изменить свойство длины:

food.length = 10;

<< 10

Несмотря на возвращаемое значение, означающее, что свойство длины было изменено, быстрая проверка показывает, что это не так:

food.length

<< 4

Обратите внимание, что это не имеет ничего общего с объявлением переменной с использованием constвместо let. Если бы мы использовали let, мы могли бы установить foodссылку на другую строку, но мы не можем изменить ни одно из ее свойств. Невозможно изменить какие-либо свойства примитивных типов данных, потому что они неизменяемы.

Изменчивость и объекты в JavaScript

И наоборот, все объекты в JavaScript изменяемы, что означает, что их свойства могут быть изменены, даже если они объявлены с использованием const (помните letи constконтролируйте только то, может ли переменная быть переназначена, и не имеет ничего общего с изменчивостью). Например, мы можем изменить первый элемент массива, используя следующий код:

const food = ['🍏','🍌','🥕','🍩'];

food[0] = '🍎';

food

<< ['🍎','🍌','🥕','🍩']

Обратите внимание, что это изменение все же произошло, несмотря на то, что мы объявили переменную foodс помощью const. Это показывает, что использование const не предотвращает мутацию объектов.

Мы также можем изменить свойство длины массива, даже если оно было объявлено с помощью const:

food.length = 2;

<< 2

food

<< ['🍎','🍌']

Копирование по ссылке

Помните, что когда мы присваиваем переменные литералам объектов, переменные будут ссылаться на совершенно разные объекты, даже если они выглядят одинаково:

const ghostbusters = {number: 4};

const tmnt = {number: 4};

переменные, ссылающиеся на разные объекты

Но если мы назначим переменную fantastic4другой переменной, они обе будут ссылаться на один и тот же объект:

const fantastic4 = tmnt;

Это присваивает переменной fantastic4ссылку на тот же объект, на который tmntссылается переменная, а не на совершенно другой объект.

переменные, ссылающиеся на один и тот же объект

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

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

Итак, если Человек-паук присоединится к Фантастической четверке, мы можем обновить numberзначение в объекте:

fantastic4.number = 5;

Это мутация, потому что мы изменили numberсвойство, а не установили fantastic4ссылку на новый объект.

Это вызывает у нас проблему, потому что numberсвойство tmntтакже изменится, возможно, даже без нашего ведома:

tmnt.number

<< 5

Это связано с тем, что оба tmntи fantastic4ссылаются на один и тот же объект, поэтому любые мутации, внесенные в любой из них tmnt, fantastic4повлияют на них обоих.

Это подчеркивает важную концепцию JavaScript: когда объекты копируются по ссылке и впоследствии мутируются, мутация влияет на любые другие переменные, которые ссылаются на этот объект. Это может привести к непреднамеренным побочным эффектам и ошибкам, которые трудно отследить.

Оператор спреда спешит на помощь!

Так как же сделать копию объекта, не создавая ссылку на исходный объект? Ответ заключается в использовании оператора распространения!

Оператор распространения был введен для массивов и строк в ES2015 и для объектов в ES2018. Это позволяет вам легко сделать поверхностную копию объекта, не создавая ссылку на исходный объект.

В приведенном ниже примере показано, как мы можем установить переменную fantastic4для ссылки на копию tmntобъекта. Эта копия будет точно такой же, как tmntобъект, но fantastic4будет ссылаться на совершенно новый объект. Это делается путем размещения имени копируемой переменной внутри литерала объекта с оператором распространения перед ним:

const tmnt = {number: 4};

const fantastic4 = {...tmnt};

Что мы на самом деле сделали здесь, так это присвоили переменную fantastic4новому объектному литералу, а затем использовали оператор расширения для копирования всех перечисляемых свойств объекта, на который ссылается tmntпеременная. Поскольку эти свойства являются значениями, они копируются в fantastic4объект по значению, а не по ссылке.

переменные, ссылающиеся на разные объекты

Теперь любые изменения, внесенные в любой объект, не повлияют на другой. Например, если мы обновим numberсвойство fantastic4переменной до 5, это не повлияет на tmntпеременную:

fantastic4.number = 5;

fantastic4.number

<< 5

tmnt.number

<< 4

Изменения не влияют на другой объект

Оператор распространения также имеет полезную сокращенную нотацию, которую можно использовать для создания копий объекта, а затем внесения некоторых изменений в новый объект в одной строке кода.

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

const leonardo = {

animal: 'turtle’,

color: 'blue’,

shell: true,

ninja: true,

weapon: 'katana’

}

Все остальные черепахи имеют одинаковые свойства, за исключением свойств weaponи color, которые различаются для каждой черепахи. Имеет смысл сделать копию объекта, на который leonardoссылается, используя оператор распространения, а затем изменить свойства weaponand color, например так:

const michaelangelo = {...leonardo};

michaelangelo.weapon = 'nunchuks’;

michaelangelo.color = 'orange’;

Мы можем сделать это в одной строке, добавив свойства, которые мы хотим изменить, после ссылки на объект распространения. Вот код для создания новых объектов для переменных donatelloи raphael:

const donatello = {...leonardo, weapon: 'bo staff’, color: 'purpple’}

const raphael = {...leonardo, weapon: 'sai’, color: 'purple’}

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

Мутации — это плохо?

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

У мутаций плохая репутация, но они не обязательно плохи сами по себе. На самом деле, если вы создаете динамическое веб-приложение, в какой-то момент оно должно измениться. Вот буквальное значение слова «динамический»! Это означает, что где-то в вашем коде должны быть какие-то мутации. Сказав это, чем меньше мутаций, тем более предсказуемым будет ваш код, что упрощает его поддержку и снижает вероятность возникновения каких-либо ошибок.

Особенно токсичной комбинацией является копирование по ссылке и мутации. Это может привести к побочным эффектам и ошибкам, о которых вы даже не подозреваете. Если вы измените объект, на который ссылается другая переменная в вашем коде, это может вызвать множество проблем, которые будет трудно отследить. Суть в том, чтобы попытаться свести использование мутаций к минимуму и отслеживать, какие объекты были мутированы.

В функциональном программировании чистая функция — это функция, которая не вызывает никаких побочных эффектов, а мутации — одна из основных причин побочных эффектов.

Золотое правило — избегать копирования любых объектов по ссылке. Если вы хотите скопировать другой объект, используйте оператор распространения, а затем внесите любые изменения сразу после создания копии.

Делитесь нашими материалами с друзьями!

 

 

Заказать разработку сайта