Руководство по присвоению и мутации переменных в JavaScript

Руководство по присвоению и мутации переменных в JavaScript Программирование и разработка

В мире JavaScript вы довольно часто слышите о мутациях, но что они собой представляют и настолько ли они злы, какими кажутся?

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

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

Типы данных

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

  • цифры, такие как 3, 0, −4,0.625
  • строки, такие как ’Hello’, «World», `Hi`,’’
  • Логические значения trueиfalse
  • null
  • undefined
  • символы — уникальный жетон, который гарантированно никогда не столкнется с другим символом
  • BigInt — для работы с большими целыми значениями

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

Присвоение переменной

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

const bears = 3;

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

Распространенная метафора для переменных — это одна из коробок с метками

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

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

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

let musketeers = 3;

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

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

bears === musketeers
<< true

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

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

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

const ghostbusters = { number: 4 };

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

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

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

let tmnt = { number: 4 };

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

ghostbusters === tmnt
<< false

Несмотря на то, что переменные ghostbustersи tmntвыглядят так

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

Когда 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 используется для объявления переменной, его можно переназначить для ссылки на другое значение позже в нашем коде. Например, мы объявили переменную musketeersusing let, поэтому мы можем изменить значение, на которое musketeersссылается. Если бы Д’Артаньян присоединился к мушкетерам, их число увеличилось бы до 4:

musketeers = 4;

Когда ключевое слово let используется для объявления переменной

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

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

tmnt = {number: 5};

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

Читайте также:  5 главных причин изучить JavaScript

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

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

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

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

const stooges = musketeers;

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

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

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

Однако это невозможно в собственном JavaScript

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

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

Мутации

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

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

const food = "cake";

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

food[] = "f";
<< "f"

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

food
<< "cake"

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

food.length = 10;
<< 10

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

food.length
<< 4

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

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

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

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

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

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

food.length = 2;
<< 2
food
<< ['🍎','🍌']

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

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

const ghostbusters = {number: 4};
const tmnt = {number: 4};

Помните, что когда мы назначаем переменные объектным литералам

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

const fantastic4 = tmnt;

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

Это назначает переменной fantastic4 ссылку на тот же объект

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

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

Читайте также:  Chef или Puppet: сравнение инструментов управления конфигурацией с открытым исходным кодом

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

fantastic4.number = 5;

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

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

tmnt.number
<< 5

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

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

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

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

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

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

const tmnt = {number: 4};
const fantastic4 = {...tmnt};

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

Фактически мы здесь присвоили переменную 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ссылки, используя оператор размытия, а затем изменить weaponи 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'}

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

Плохие мутации?

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

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

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

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

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

Оцените статью
bestprogrammer.ru
Добавить комментарий