Эта статья была первоначально опубликована на Medium .
TL; DR: в JavaScript нет указателей, и ссылки работают не так, как обычно в большинстве других популярных языков программирования. В JavaScript просто НЕ возможно иметь ссылку из одной переменной в другую. И, только составные значения (например, Object или Array) могут быть назначены по ссылке.
В статье используются следующие термины:
- скаляр — единичное значение или единица данных (например, целое число, логическое значение, строка)
- составной — состоит из нескольких значений (например, массив, объект, набор)
- примитив — это прямое значение, в отличие от ссылки на то, что содержит реальное значение.
Скалярные типы JavaScript являются примитивами, но некоторые языки, такие как Ruby, имеют скалярные ссылочные типы. Обратите внимание, что в JavaScript скалярные значения примитивов неизменны, а составные значения изменчивы.
Нижняя линия:
- Значение
typeof
назначенное переменной, решает, будет ли значение сохранено с назначением по значению или назначением по ссылке. - При назначении переменной скалярные значения примитива (Number, String, Boolean, undefined, null, Symbol) присваиваются по значению, а составные значения присваиваются по ссылке.
- Ссылки в JavaScript указывают только на содержащиеся значения, а НЕ на другие переменные или ссылки.
- В JavaScript скалярные примитивные значения неизменны, а составные значения изменчивы.
Быстрый пример присвоения по значению:
В приведенном ниже фрагменте кода мы присваиваем скалярное примитивное значение (число) переменной и, таким образом, здесь применяется присвоение по значению. Во-первых, переменная batman
инициализируется, и когда переменной superman
присваивается значение, сохраненное в batman
, она создает новую копию значения и сохраняет ее. Когда переменная superman
модифицируется, batman
остается без изменений, так как они указывают на разные значения.
var batman = 7; var superman = batman; //assign-by-value superman++; console.log(batman); //7 console.log(superman); //8
Быстрый пример назначения по ссылке:
В приведенном ниже фрагменте кода мы присваиваем составное значение (массив) переменной и, таким образом, здесь применяется назначение по ссылке. Переменные flash
и quicksilver
являются ссылками на одно и то же значение (также называемое общим значением). Ссылки будут указывать на обновленное значение при изменении общего значения.
var flash = [8,8,8]; var quicksilver = flash; //assign-by-reference quicksilver.push(0); console.log(flash); //[8,8,8,0] console.log(quicksilver); //[8,8,8,0]
Как создать новую ссылку
Когда составное значение в переменной переназначается, создается новая ссылка. В JavaScript, в отличие от большинства других популярных языков программирования, ссылки являются указателями на значения, хранящиеся в переменных, а НЕ указателями на другие переменные или ссылки.
var firestorm = [3,6,3]; var atom = firestorm; //assign-by-reference console.log(firestorm); //[3,6,3] console.log(atom); //[3,6,3] atom = [9,0,9]; //value is reassigned (create new reference) console.log(firestorm); //[3,6,3] console.log(atom); //[9,0,9]
Как работают ссылки, когда значения передаются как параметры функции
В приведенном ниже фрагменте кода переменная magneto
является составным значением (массивом), поэтому она присваивается переменной (аргумент функции) x
в качестве ссылки.
Метод Array.prototype.push
вызываемый внутри IIFE, изменяет значение в переменной magneto
через ссылку JavaScript. Но переназначение переменной x
создает новую ссылку, и дальнейшие модификации к ней НЕ влияют на ссылку на переменную magneto
.
var magneto = [8,4,8]; (function(x) { //IIFE x.push(99); console.log(x); //[8,4,8,99] x = [1,4,1]; //reassign variable (create new reference) x.push(88); console.log(x); //[1,4,1,88] })(magneto); console.log(magneto); //[8,4,8,99]
Как изменить исходное значение в составной переменной, передаваемой как аргумент функции через ссылку JavaScript
Решением здесь будет изменение существующего составного значения, на которое указывает ссылка. В приведенном ниже фрагменте кода переменная wolverine
является составным значением (массив), и при вызове IIFE переменная (аргумент функции) x
назначается ссылкой.
Свойство Array.prototype.length
можно использовать для создания пустого массива, установив его значение в 0
. Таким образом, переменная росомаха изменяется на новое значение, установленное в переменной x
через ссылку JavaScript.
var wolverine = [8,7,8]; (function(x) { //IIFE x.length = 0; //make empty array object x.push(1,4,7,2); console.log(x); //[1,4,7,2] })(wolverine); console.log(wolverine); //[1,4,7,2]
Как сохранить составное значение через присвоение по значению
Решением здесь будет создание ручной копии составного значения, а затем присвоение скопированного значения переменной. Следовательно, ссылка на присвоенное значение НЕ указывает на исходное значение.
Рекомендуемый подход для создания (поверхностной) копии составного значения (объекта Array) состоит в том, чтобы вызвать для Array.prototype.slice
метод Array.prototype.slice
без передаваемых аргументов.
var cisco = [7,4,7]; var zoom = cisco.slice(); //create shallow copy cisco.push(77,33); console.log(zoom); //[7,4,7] console.log(cisco); //[7,4,7,77,33]
Как сохранить скалярное значение примитива через присвоение по ссылке?
Решение в этом случае заключалось бы в том, чтобы обернуть скалярное примитивное значение в составное значение (т.е. объект или массив) в качестве значения его свойства. Таким образом, он может быть назначен по ссылке. В приведенном ниже фрагменте кода скалярное значение примитива с переменной speed
задается как свойство объекта flash
. Следовательно, он присваивается по ссылке при вызове IIFE переменной (аргумент функции) x
.
var flash = { speed: 88 }; (function (x) { //IIFE x.speed = 55; })(flash); console.log(flash.speed); //55
Резюме
Хорошее понимание ссылок в JavaScript может помочь разработчикам избежать многих распространенных ошибок и написать лучший код.
Удачного кодирования!