- Вопрос или проблема
- JavaScript передаёт примитивные типы по значению, а объектные типы по ссылке
- Обновление
- Конструкция списков аргументов
- Доступ к свойствам объекта
- О ссылках
- Определение “объекта”
- Нет передачи по ссылке
- Объекты
- Присвоение переменной против мутации объекта
- Ответ или решение
- Примитивные типы
- Ссылочные (объектные) типы
- Но что происходит, если мы присваиваем новую ссылку?
- Итог
- Спецификация JavaScript
Вопрос или проблема
Примитивные типы (число, строка и т. д.) передаются по значению. Однако объекты не совсем однозначны, так как их можно передавать как по значению (в этом случае мы считаем, что переменная, содержащая объект, является ссылкой на объект), так и по ссылке (когда мы считаем, что переменная объекта содержит сам объект).
Хотя в конечном итоге это не имеет значения, мне хочется знать, как правильно представить соглашения о передаче аргументов. Есть ли отрывок из спецификации JavaScript, который определяет, каковы должны быть семантика в этом отношении?
Интересно, в JavaScript. Рассмотрим этот пример:
function changeStuff(a, b, c)
{
a = a * 10;
b.item = "changed";
c = {item: "changed"};
}
var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num);
console.log(obj1.item);
console.log(obj2.item);
Это приводит к следующему выводу:
10
changed
unchanged
- Если
obj1
не был бы ссылкой вообще, то изменениеobj1.item
не повлияло бы наobj1
вне функции. - Если бы аргумент был правильной ссылкой, то всё бы изменилось.
num
был бы равен100
, аobj2.item
читал бы"changed"
. Вместо этого,num
остаётся равным10
, аobj2.item
остаётся равным"unchanged"
.
Таким образом, ситуация такова, что элемент, переданный в функцию, передаётся по значению. Но элемент, который передаётся по значению, является самой ссылкой. Технически это называется передачей по совместному использованию.
На практике это означает, что если вы измените сам параметр (как с num
и obj2
), это не повлияет на элемент, который был передан в параметр. Но если вы измените внутренности параметра, это будет распространено обратно (как с obj1
).
</div><div class="s-prose js-post-body" itemprop="text">
Всегда происходит передача по значению, но для объектов значением переменной является ссылка. Из-за этого, когда вы передаёте объект и изменяете его члены, эти изменения сохраняются и за пределами функции. Это делает так, что выглядит так, будто происходит передача по ссылке. Но если вы измените значение переменной объекта, вы увидите, что изменение не сохраняется, что доказывает, что на самом деле происходит передача по значению.
Пример:
function changeObject(x) {
x = { member: "bar" };
console.log("in changeObject: " + x.member);
}
function changeMember(x) {
x.member = "bar";
console.log("in changeMember: " + x.member);
}
var x = { member: "foo" };
console.log("before changeObject: " + x.member);
changeObject(x);
console.log("after changeObject: " + x.member); /* изменение не сохранилось */
console.log("before changeMember: " + x.member);
changeMember(x);
console.log("after changeMember: " + x.member); /* изменение сохранилось */
Вывод:
before changeObject: foo
in changeObject: bar
after changeObject: foo
before changeMember: foo
in changeMember: bar
after changeMember: bar
Переменная не «содержит» объект; она содержит ссылку. Вы можете присвоить эту ссылку другой переменной, и теперь обе ссылаются на один и тот же объект. Всегда происходит передача по значению (даже когда это значение — ссылка…).
Нет способа изменить значение переменной, переданной в качестве параметра, что было бы возможно, если бы JavaScript поддерживал передачу по ссылке.
Моё мнение… Так я это понимаю. (Не стесняйтесь исправлять меня, если я не прав)
Время избавиться от всего, что вы знаете о передаче по значению/ссылке.
Потому что в JavaScript не имеет значения, передано ли по значению или по ссылке или как-то иначе.
Важно различие между мутацией и присвоением параметров, переданных в функцию.
Окей, давайте я постараюсь объяснить, что я имею в виду. Допустим, у вас есть несколько объектов.
var object1 = {};
var object2 = {};
То, что мы сделали, это «присвоение»… Мы присвоили 2 отдельных пустых объекта переменным «object1» и «object2».
Теперь, допустим, что нам больше нравится object1… Итак, мы «присваиваем» новую переменную.
var favoriteObject = object1;
Следовательно, по какой-то причине мы решаем, что нам больше нравится объект 2. Поэтому мы делаем небольшую переассигацию.
favoriteObject = object2;
Ничего не произошло с object1 или object2. Мы не изменили никаких данных. Всё, что мы сделали, это переопределили, что наш любимый объект. Важно помнить, что object2 и favoriteObject оба присвоены одному и тому же объекту. Мы можем изменить этот объект через любую из этих переменных.
object2.name="Fred";
console.log(favoriteObject.name) // Выводит Fred
favoriteObject.name="Joe";
console.log(object2.name); // Выводит Joe
Окей, теперь давайте посмотрим на примитивы, такие как строки, например:
var string1 = 'Hello world';
var string2 = 'Goodbye world';
Снова выбираем любимое.
var favoriteString = string1;
Теперь, что если мы хотим изменить наш favoriteString??? Что произойдёт???
favoriteString = 'Hello everyone';
console.log(favoriteString); // Выводит 'Hello everyone'
console.log(string1); // Выводит 'Hello world'
Ой-ой…. Что случилось. Мы не могли изменить string1, изменив favoriteString… Почему?? Потому что мы не изменили наш строковый объект. Мы просто «ПЕРЕПРИСВОИЛИ» переменной favoriteString новую строку. Это фактически отключило его от string1. В предыдущем примере, когда мы переименовали наш объект, мы ничего не присваивали. (Что ж, не самой переменной, … однако мы присвоили свойству name новую строку.) Вместо этого мы изменили объект, который поддерживает связи между 2 переменными и основными объектами. (Даже если бы мы хотели изменить или мутацию сам объект строки, мы не смогли бы, потому что строки фактически неизменяемы в JavaScript.)
Теперь перейдем к функциям и передаче параметров…. Когда вы вызываете функцию и передаёте параметр, то по сути вы «присваиваете» новую переменную, и это работает точно так же, как если бы вы использовали знак равно (=).
Возьмите эти примеры.
var myString = 'hello';
// Присвоить новой переменной (так же, как когда вы передаёте в функцию)
var param1 = myString;
param1 = 'world'; // Переопределение
console.log(myString); // Выводит 'hello'
console.log(param1); // Выводит 'world'
Теперь то же самое, но с функцией:
function myFunc(param1) {
param1 = 'world';
console.log(param1); // Выводит 'world'
}
var myString = 'hello';
// Вызываем myFunc и присваиваем param1 значению myString так же, как param1 = myString
myFunc(myString);
console.log(myString); // выводит 'hello'
Хорошо, теперь давайте приведем несколько примеров, используя объекты, вместо этого… сначала, без функции.
var myObject = {
firstName: 'Joe',
lastName: 'Smith'
};
// Присвоить новой переменной (так же, как когда вы передаёте в функцию)
var otherObj = myObject;
// Давайте изменим наш объект
otherObj.firstName="Sue"; // Полагаю, Джо решил стать девушкой
console.log(myObject.firstName); // Выводит 'Sue'
console.log(otherObj.firstName); // Выводит 'Sue'
// Теперь давайте переопределим переменную
otherObj = {
firstName: 'Jack',
lastName: 'Frost'
};
// Теперь otherObj и myObject присвоены 2 совершенно разным объектам
// И мутация одного объекта не влияет на другой
console.log(myObject.firstName); // Выводит 'Sue'
console.log(otherObj.firstName); // Выводит 'Jack';
Теперь то же самое, но с вызовом функции:
function myFunc(otherObj) {
// Давайте изменим наш объект
otherObj.firstName="Sue";
console.log(otherObj.firstName); // Выводит 'Sue'
// Теперь давайте переоснворим переменную
otherObj = {
firstName: 'Jack',
lastName: 'Frost'
};
console.log(otherObj.firstName); // Выводит 'Jack'
// Снова, otherObj и myObject присвоены 2 очень разным объектам
// И мутация одного объекта не волшебным образом изменяет другой
}
var myObject = {
firstName: 'Joe',
lastName: 'Smith'
};
// Вызывает myFunc и присваивает otherObj значению myObject так же, как otherObj = myObject
myFunc(myObject);
console.log(myObject.firstName); // Выводит 'Sue', как и прежде
Хорошо, если вы прочитали этот пост полностью, возможно, теперь вы лучше понимаете, как работают вызовы функций в JavaScript. Неважно, передано что-то по ссылке или по значению… Важно различие между присвоением и мутацией.
Всегда помните, что знак равенства (=) означает присвоение.
Всегда помните, что передача параметра функции в JavaScript также означает присвоение.
Они одинаковы, и 2 переменные связаны точно так же (что означает, что они не связаны, если не считать, что они присвоены одному и тому же объекту).
Единственное время, когда «изменение переменной» влияет на другую переменную, это когда подлежащий объект изменяется (в этом случае вы не изменили переменную, а сам объект).
Нет смысла делать различие между объектами и примитивами, потому что это работает точно так же, как если бы вы не имели функции и просто использовали знак равенства, чтобы присвоить новому значению.
Единственная уловка заключается в случае, когда имя переменной, которое вы передаёте функции, совпадает с именем параметра функции. Когда это происходит, вы должны рассматривать параметр внутри функции как совершенно новую переменную, принадлежащую функции (потому что это так)
function myFunc(myString) {
// myString является частным и не влияет на внешнюю переменную
myString = 'hello';
}
var myString = 'test';
myString = myString; // Ничего не делает, myString все ещё 'test';
myFunc(myString);
console.log(myString); // Выводит 'test'
Эти фразы/концепции были изначально определены задолго до создания JS и не описывают семантику для JavaScript. Я думаю, что попытки применить их к JS создают больше путаницы, чем понимания.
Так что не зацикливайтесь на “передаче по ссылке/значению”.
Учтите следующее:
- Переменные — это указатели на значения.
- Переопределение переменной просто указывает этот указатель на новое значение.
- Переопределение переменной никогда не повлияет на другие переменные, которые указывают на тот же объект, потому что каждая переменная имеет свой указатель.
Так что, если бы мне пришлось дать этому имя, я бы сказал “передача по указателю” — мы не работаем с указателями в JS, но основная система это делает.
// код
var obj = {
name: 'Fred',
num: 1
};
// иллюстрация
'Fred'
/
/
(obj) ---- {}
\
\
1
// код
obj.name="George";
// иллюстрация
'Fred'
(obj) ---- {} ----- 'George'
\
\
1
// код
obj = {};
// иллюстрация
'Fred'
(obj) {} ----- 'George'
| \
| \
{ } 1
// код
var obj = {
text: 'Hello world!'
};
/* параметры функции получают свой собственный указатель на
* аргументы, которые передаются, так же как и любая другая переменная */
someFunc(obj);
// иллюстрация
(caller scope) (someFunc scope)
\ /
\ /
\ /
\ /
\ /
{ }
|
|
|
'Hello world'
Некоторые финальные замечания:
- Фразы “передача по значению/ссылке” используются только для описания поведения языка, а не обязательно фактической реализации. В результате этого абстрагирования теряются критически важные детали, необходимые для достойного объяснения, что неизбежно приводит к текущей ситуации, где один единственный термин не описывает адекватно фактическое поведение без дополнительной информации.
- Иногда кажется, что примитивы соблюдают специальные правила, в то время как объекты — нет, но примитивы просто являются концом цепи указателей.
- В качестве финального примера подумайте, почему попытка очистить массив не работает так, как ожидалось.
var a = [1, 2];
var b = a;
a = [];
console.log(b); // [1,2]
// не работает, потому что `b` все еще указывает на оригинальный массив
Объект за пределами функции передается в функцию, предоставляя ссылку на внешний объект.
Когда вы используете эту ссылку для манипуляции с его объектом, объект снаружи таким образом затрагивается. Однако, если внутри функции вы решили указать ссылку на что-то другое, вы не повлияли на объект снаружи вообще, потому что вы лишь перенаправили ссылку на что-то другое.
Думайте об этом так: всегда происходит передача по значению. Однако значение объекта — это не сам объект, а ссылка на этот объект.
Вот пример, передаём число (примитивный тип)
function changePrimitive(val) {
// На этом этапе в памяти находятся два '10'.
// Изменение одного не повлияет на другое
val = val * 10;
}
var x = 10;
changePrimitive(x);
// x === 10
Повторение этого с объектом дает разные результаты:
function changeObject(obj) {
// На этом этапе в памяти находятся две ссылки (x и obj),
// но обе указывают на один и тот же объект.
// изменение объекта изменит underlying object, на который
// указывают как x, так и obj
obj.val = obj.val * 10;
}
var x = { val: 10 };
changeObject(x);
// x === { val: 100 }
Ещё один пример:
function changeObject(obj) {
// Снова, существует две ссылки (x и obj) в памяти,
// которые указывают на один и тот же объект.
// сейчас мы создаём совершенно новый объект и присваиваем его.
// теперь ссылка obj указывает на новый объект.
// ссылка x не изменяется.
obj = { val: 100 };
}
var x = { val: 10 };
changeObject(x);
// x === { val: 10}
Очень подробное объяснение по копированию, передаче и сравнению по значению и по ссылке содержится в в этой главе книги “JavaScript: The Definitive Guide”.
Прежде чем мы уйдем от темы
манипуляции объектами и массивами
по ссылке, нам нужно прояснить
пункт о терминологии.Фраза “передача по
ссылке” может иметь несколько значений.
Для некоторых читателей данная фраза относится
к технике вызова функции, которая
позволяет функции присваивать новые значения
своим аргументам и иметь эти
измененные значения видимыми вне
функции. Это не то, как этот термин
используется в этой книге.Здесь мы имеем в виду
просто, что ссылка на объект
или массив — не сам объект —
передаётся в функцию. Функция
может использовать ссылку для изменения
свойств объекта или элементов
массива. Но если функция
перезапишет ссылку новой
ссылкой на новый объект или массив,
это изменение не будет видно
вне функции.Читатели,
знакомые с другим значением
этого термина, могут предпочесть сказать, что
объекты и массивы передаются по
значению, но передаваемое значение на самом деле является ссылкой, а не
самим объектом.
JavaScript всегда передаёт по значению; всё имеет тип значения.
Объекты являются значениями, и функции-члены объектов сами являются значениями (помните, что функции являются объектами первого класса в JavaScript). Также, касаясь концепции, что всё в JavaScript — это объект, это неверно. Строки, символы, числа, логические значения, null и undefined являются примитивами.
Иногда они могут использовать некоторые методы и свойства, унаследованные от своих базовых прототипов, но это только для удобства. Это не значит, что они сами являются объектами. Попробуйте следующее для справки:
x = "test";
console.log(x.foo);
x.foo = 12;
console.log(x.foo);
В обоих случаях console.log
вы получите значение undefined
.
В JavaScript тип значения исключительно контролирует, будет ли это значение передано по копии значения или по копии ссылки.
Примитивные значения всегда передаются/передаются по копии значения:
null
undefined
- строка
- число
- логический
- символ в
ES6
Составные значения всегда передаются/передаются по копии ссылки
- объекты
- массивы
- функции
Например:
var a = 2;
var b = a; // `b` всегда является копией значения в `a`
b++;
a; // 2
b; // 3
var c = [1,2,3];
var d = c; // `d` является ссылкой на общее значение `[1,2,3]`
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]
В приведённом выше коде, поскольку 2
является скалярным примитивом, a
содержит одну исходную копию этого значения, а b
присваивается другая копия значения. Когда вы изменяете b
, вы никаким образом не изменяете значение в a
.
Но как c
, так и d
являются отдельными ссылками на одно и то же общее значение [1,2,3]
, которое является составным значением. Важно отметить, что ни c
, ни d
фактически не «принадлежат» значению [1,2,3]
— оба являются равноправными ссылками на это значение. Таким образом, когда вы используете любую из этих ссылок для изменения (.push(4)
) фактического общего значения массива
, это затрагивает только одно общее значение, и обе ссылки будут указывать на новое измененное значение [1,2,3,4]
.
var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// позже
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
Когда мы выполняем присвоение b = [4,5,6]
, мы абсолютно ничего не делаем, чтобы повлиять на то, что a
всё ещё указывает на ([1,2,3]
). Для того чтобы это сделать, b
должен быть указателем на a
, а не ссылкой на массив
— но такой возможности в JS не существует!
function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// позже
x = [4,5,6];
x.push( 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // [1,2,3,4] а не [4,5,6,7]
Когда мы передаём аргумент a
, он присваивает копию ссылки a
ссылке x
. x
и a
— это отдельные ссылки, указывающие на одно и то же значение [1,2,3]
. Теперь внутри функции мы можем использовать эту ссылку, чтобы изменить само значение (push(4)
). Но когда мы выполняем присвоение x = [4,5,6]
, это никоим образом не влияет на то, на что указывает исходная ссылка a
— она по-прежнему указывает на (теперь изменённое) значение [1,2,3,4]
.
Чтобы эффективно передать составное значение (например, массив
) по копии значения, вы должны вручную создать его копию, чтобы ссылка, переданная не указывала на оригинал. Например:
foo( a.slice() );
Составное значение (объект, массив и т. д.), которое может быть передано по копии ссылки
function foo(wrapper) {
wrapper.a = 42;
}
var obj = {
a: 2
};
foo( obj );
obj.a; // 42
Здесь obj
выступает в качестве обертки для примитивного свойства a
. Когда он передается в foo(..)
, копия ссылки obj
передается и устанавливается в параметре wrapper
. Теперь мы можем использовать ссылку wrapper
, чтобы получить доступ к общему объекту и обновить его свойство. После завершения функции obj.a
будет видеть обновлённое значение 42
.
ну, это о «производительности» и «скорости», и если просто сказать — «управлении памятью» в языке программирования.
в JavaScript мы можем помещать значения на два уровня: тип1–объекты
и тип2-все другие типы значений, такие как строка
& логика
& т. д.
если представить память как ниже квадраты, в каждом из которых может храниться только одно тип2-значение:
каждое тип2-значение (зеленое) — это один квадрат, в то время как тип1-значение (синее) — это группа из них:
суть в том, что, если вы хотите указать тип2-значение, адрес очевиден, но если вы хотите сделать то же самое для тип1-значения, это совсем непросто! :
и в более сложной истории:
поэтому здесь ссылки могут спасти нас:
в то время как зеленая стрелка здесь является типичной переменной, фиолетовая — переменной объекта, так что поскольку зеленая стрелка (типичная переменная) имеет только одну задачу (и это указание на типичное значение), нам не нужно отделять ее значение от него, так что мы перемещаем зеленую стрелку с этим значением, куда бы она ни шла и во всех присвоениях, функциях и так далее…
но мы не можем сделать то же самое с фиолетовой стрелкой, мы можем переместить ячейку «john» сюда или множество других вещей…, так что фиолетовая стрелка будет оставаться на месте, и только типичные стрелки, которые были назначены ей, будут двигаться…
очень путанная ситуация, когда вы не можете понять, как ваша переменная-ссылка изменяется, давайте взглянем на очень хороший пример:
let arr = [1, 2, 3, 4, 5]; // arr — объект сейчас, и фиолетовая стрелка указывает на него
let obj2 = arr; // теперь obj2 — еще одна фиолетовая стрелка, указывающая на значение объекта arr
let obj3 = ['a', 'b', 'c'];
obj2.push(6); // первое изображение ниже — создавая новую руку, чтобы голубой круг указывал на 6
// obj2 = [1, 2, 3, 4, 5, 6]
// arr = [1, 2, 3, 4, 5, 6]
// мы изменили значение объекта голубого круга (тип1-значение), и поскольку arr и obj2 указывают на это, оба изменились
obj2 = obj3; // следующее изображение ниже - меняя направление массива obj2 от голубого круга к оранжевому кругу, так что теперь obj2 больше не [1,2,3,4,5,6], и это больше не касается изменения в нем, но мы совершенно поменяли направление, и теперь obj2 указывает на obj3
// obj2 = ['a', 'b', 'c'];
// obj3 = ['a', 'b', 'c'];
Это небольшое объяснение передачи по значению и передачи по ссылке (JavaScript). В этой концепции говорится о передаче переменной по ссылке и передаче переменной по ссылке.
Передача по значению (примитивный тип)
var a = 3;
var b = a;
console.log(a); // a = 3
console.log(b); // b = 3
a=4;
console.log(a); // a = 4
console.log(b); // b = 3
- применяется ко всем примитивным типам в JavaScript (строка, число, логика, неопределённое и null).
- a получает память (скажем, 0x001), а b создаёт копию значения в памяти (скажем, 0x002).
- Поэтому изменение значения переменной не повлияет на другую, так как они обе находятся в двух разных местах.
Передача по ссылке (объекты)
var c = { "name" : "john" };
var d = c;
console.log(c); // { "name" : "john" }
console.log(d); // { "name" : "john" }
c.name = "doe";
console.log(c); // { "name" : "doe" }
console.log(d); // { "name" : "doe" }
- Движок JavaScript присваивает объект переменной
c
, и она указывает на некоторую память, скажем, (0x012). - Когда d=c, на этом этапе
d
указывает на то же место (0x012). - Изменение значения любого изменяет значение для обеих переменных.
- Функции — это объекты.
Особый случай, передача по ссылке (объекты)
c = {"name" : "jane"};
console.log(c); // { "name" : "jane" }
console.log(d); // { "name" : "doe" }
- Оператор равенства (=) устанавливает новое пространство памяти или адрес
Семантика!! Установка конкретных определений, безусловно, сделает некоторые ответы и комментарии несовместимыми, поскольку они не описывают одно и то же, даже когда используют одни и те же слова и фразы, но важно пройти через путаницу (особенно для новых программистов).
Во-первых, существует несколько уровней абстракции, которые не все могут понять. Новым программистам, которые учились на языках 4 или 5 поколения, может быть трудно осознать концепции, знакомые программистам на ассемблере или C, не беспокоясь о «указателях на указатели на указатели». Передача по ссылке не просто означает возможность изменить ссылающийся объект, используя переменную параметра функции.
Переменная: Сочетанное понятие символа, который ссылается на значение в определённом месте в памяти. Этот термин обычно слишком перегружен, чтобы использовать его в одиночку при обсуждении деталей.
Символ: Текстовая строка, используемая для обращения к переменной (т. е. имя переменной).
Значение: Определённые биты, хранящиеся в памяти, которые ссылаются на символ переменной.
Местоположение в памяти: Где хранится значение переменной. (Сама локация представлена номером, отличным от значения, хранящегося в месте.)
Параметр функции: Переменная, объявленная в определении функции, используемая для обращения к переменным, переданным в функцию.
Аргумент функции: Переменная вне функции, которая передаётся функции вызывающей стороной.
Переменная объекта: Переменная, чьё основное базовое значение — не сам «объект», а её значение — указатель (значение адреса в памяти) на другое место в памяти, где хранятся фактические данные объекта. В большинстве языков более высокого поколения аспект «указателя» фактически скрыт благодаря автоматическому разыменованию в различных контекстах.
Переменная примитива: Переменная, значение которой — это фактическое значение. Даже эта концепция может быть усложнена автообертыванием и контекстами, похожими на объекты различных языков, но общая идея в том, что значение переменной — это фактическое значение, представленное символом переменной, а не указатель на другое местоположение в памяти.
Аргументы и параметры функции не одно и то же. Также значение переменной не является объектом переменной (как уже указывали различные люди, но, похоже, проигнорировали это). Эти различия критически важны для правильного понимания.
Передача по значению или передача по совместному использованию (для объектов): Значение аргумента функции копируется в другое место памяти, на которое ссылается символ параметра функции (независимо от того, находится ли оно в стеке или в куче). Другими словами, параметр функции получает копию значения переданного аргумента… И (критически) значение аргумента НИКОГДА НЕ ОБНОВЛЯЕТСЯ / ИЗМЕНЯЕТСЯ / МЕНЯЕТСЯ вызывающей функцией. Помните, значение переменной объекта не является самим объектом, оно является указателем на объект, поэтому передача переменной объекта по значению копирует указатель в переменную параметра функции. Значение параметра функции указывает на точно такой же объект в памяти. Данные самого объекта могут быть изменены напрямую через переменную параметра, НО значение аргумента ФУНКЦИИ НИКОГДА НЕ ОБНОВЛЯЕТСЯ, так что оно будет продолжать ссылаться на то же самое значение в течение и даже после вызова функции (даже если данные объекта изменялись или если параметр функции присваивается совершенно другому объекту). Неверно делать вывод, что аргумент функции был передан по ссылке только потому, что ссылающийся объект может быть обновлён через переменную параметра функции.
Вызов / Передача по ссылке: Значение аргумента функции может/будет обновляться напрямую соответствующим параметром функции. Если это поможет, параметр функции становится эффективным «псевдонимом» для аргумента — они фактически ссылаются на то же значение в той же области памяти. Если аргумент функции является переменной объекта, возможность изменить данные объекта не отличается от случая передачи по значению, поскольку параметр функции всё ещё будет указывать на тот же объект, что и аргумент. Но в случае переменной объекта, если параметр функции установлен на совершенно другой объект, то аргумент также будет указывать на другой объект — этого не происходит в случае передачи по значению.
JavaScript не передаёт по ссылке. Если вы прочитаете внимательно, вы поймёте, что все противоположные мнения неправильно понимают, что подразумевается под передачей по значению и ошибочно делают вывод, что способность обновлять данные объекта через параметр функции является синонимом «передачи по значению».
Клонирование/копирование объектов: Создаётся новый объект, и данные оригинального объекта копируются. Это может быть глубоким или поверхностным копированием, но суть в том, что создаётся новый объект. Создание копии объекта — это отдельная концепция от передачи по значению. Некоторые языки проводят различия между классом объекта и структурами (или подобными), и могут иметь различное поведение при передаче переменных различных типов. Но JavaScript не делает ничего подобного автоматически при передаче переменных объектов. Но отсутствие автоматического клонирования объектов не означает, что происходит передача по ссылке.
делясь тем, что я знаю о ссылках в JavaScript
В JavaScript, когда вы присваиваете объект переменной, значением, присваиваемым переменной, является ссылка на объект:
var a = {
a: 1,
b: 2,
c: 3
};
var b = a;
// b.c ссылается на значение a.c
console.log(b.c) // Вывод: 3
// Изменение значения b.c
b.c = 4
// Также изменяет значение a.c
console.log(a.c) // Вывод: 4
Наблюдение: Если нет никакой возможности для наблюдателя исследовать подлежащую память движка, невозможно определить, копируется ли неизменяемое значение или передаётся ссылка.
JavaScript более-менее нейтрален по отношению к подлежащей модели памяти. Не существует такого понятия, как ссылка². JavaScript имеет значения. Две переменные могут содержать одно и то же значение (или точнее: два записных поля могут привязываться к одному и тому же значению). Единственный тип значений, которые можно нарушить, — это объекты, через их абстракции [[Get]] и [[Set]].
Если вы забудете о компьютерах и памяти, это всё, что вам нужно, чтобы описать поведение JavaScript, и это позволяет вам понять спецификацию.
let a = { prop: 1 };
let b = a; // a и b содержат одно и то же значение
a.prop = "test"; // Объект меняется, это можно увидеть как a, так и b
b = { prop: 2 }; // b теперь содержит другое значение
Теперь вы можете задать себе вопрос, как две переменные могут хранить одно и то же значение на компьютере. Вы можете тогда заглянуть в исходный код движка JavaScript и, вероятно, найти что-то, что программист языка, на котором был написан движок, назвал бы ссылкой.
Таким образом, на самом деле можно сказать, что JavaScript — это “передача по значению”, в то время как значение может быть общим, и вы можете сказать, что JavaScript использует “передачу по ссылке”, что может быть полезной логической абстракцией для программистов из языков низкого уровня, или вы можете назвать поведение “передачей по совместному использованию”.
Поскольку в JavaScript не существует понятия ссылки, ни одно из этих определений не является неправильным, но и не точным. Поэтому я не думаю, что ответ особенно полезен для поиска.
² Термин Ссылка в спецификации — это не ссылка в традиционном смысле. Это контейнер объекта и имя свойства, и это промежуточное значение (например, a.b
вычисляется как Reference { value = a, name = "b" }
). Термин ссылка также иногда появляется в спецификации в несвязанных разделах.
Всё передаётся по значению.
Простые типы передаются по значению (т. е. копия фактического значения переменной передаётся функции).
Сложные типы (объекты) передаются как «указатель на объект». Поэтому фактически вы передаёте указатель, который передаётся по значению (это адрес, числовое значение, как и любое другое). Очевидно, что если вы попытаетесь изменить свойство объекта внутри функции, изменение будет отражено даже вне такой функции. Это происходит потому, что вы обращаетесь к свойству через указатель, который указывает на уникальную копию этого свойства.
“передача указателя по значению” и “передача объекта по ссылке” — это одно и то же.
Документация MDN четко объясняет это, не будучи слишком многословной:
Параметры вызова функции — это аргументы функции.
Аргументы передаются функциям по значению. Если функция изменяет
значение аргумента, это изменение не отражается глобально или в
вызывающей функции. Однако ссылки на объекты также являются значениями и
они особенные: если функция изменяет свойства упоминаемого объекта,
это изменение видно вне функции, (…)
Источник: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description
Мой простой способ понять это…
-
Когда вы вызываете функцию, вы передаёте содержимое (ссылку или
значение) переменных аргументов, а не сами переменные.var var1 = 13; var var2 = { prop: 2 }; // 13 и содержимое var2 (ссылка) передаются здесь foo(var1, var2);
-
Внутри функции переменные параметров,
inVar1
иinVar2
, получают содержимое, которое передаётся.function foo(inVar1, inVar2){ // изменение содержимого inVar1 и inVar2 не повлияет на переменные снаружи inVar1 = 20; inVar2 = { prop: 7 }; }
-
Поскольку
inVar2
получила ссылку на{ prop: 2 }
, вы можете изменить значение свойства объекта.function foo(inVar1, inVar2){ inVar2.prop = 7; }
JavaScript передаёт примитивные типы по значению, а объектные типы по ссылке
Теперь люди любят бесконечно спорить, является ли “передача по ссылке” правильным способом описать то, что на самом деле делают Java и другие. Суть в следующем:
- Передача объекта не копирует объект.
- Объект, переданный функции, может иметь свои члены изменены функцией.
- Простое значение, переданное функции, не может быть изменено функцией. Копия создаётся.
В моей книге это называется передачей по ссылке.
— Brian Bi – Какие языки программирования передаются по ссылке?
Обновление
Вот опровержение этому:
Передача аргументов функции в JavaScript аналогична передаче
параметров по указателю в C:
/*
Следующая программа на C демонстрирует, как аргументы
передаются в функции JavaScript аналогично передаче по указателю в C. Исходный тестовый случай JavaScript от @Shog9 следует за переводом
кода в C. Это должно прояснить для
тех, кто переходит с C на JavaScript.
function changeStuff(num, obj1, obj2)
{
num = num * 10;
obj1.item = "changed";
obj2 = {item: "changed"};
}
var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num);
console.log(obj1.item);
console.log(obj2.item);
Это приводит к следующему выводу:
10
changed
unchanged
*/
#include <stdio.h>
#include <stdlib.h>
struct obj {
char *item;
};
void changeStuff(int *num, struct obj *obj1, struct obj *obj2)
{
// заставить указатель указывать на новое местоположение в памяти
// где хранится новое целочисленное значение
int *old_num = num;
num = malloc(sizeof(int));
*num = *old_num * 10;
// сделать свойство структуры, на которое ссылается указатель,
// указывать на новое значение
obj1->item = "changed";
// заставить указатель указывать на новое местоположение в памяти
// где хранится новое значение структуры
obj2 = malloc(sizeof(struct obj));
obj2->item = "changed";
free(num); // конец области видимости
free(obj2); // конец области видимости
}
int num = 10;
struct obj obj1 = { "unchanged" };
struct obj obj2 = { "unchanged" };
int main()
{
// передача указателей по значению: указатели
// будут скопированы в список аргументов
// вызываемой функции, и скопированные
// указатели будут указывать на те же значения
// что и оригинальные указатели
changeStuff(&num, &obj1, &obj2);
printf("%d\n", num);
puts(obj1.item);
puts(obj2.item);
return 0;
}
Для юристов языков программирования я ознакомился со следующими разделами ECMAScript 5.1 (которые легче читать, чем последнее издание) и даже задавал вопрос на почтовом списке ECMAScript.
Кратко: Всё передаётся по значению, но свойства объектов — это ссылки, а определение объекта зловеще недостаточно в стандарте.
Конструкция списков аргументов
Раздел 11.2.4 “Списки аргументов” говорит следующее о создании списка аргументов, состоящего только из 1 аргумента:
Производство ArgumentList : AssignmentExpression оценивается следующим образом:
- Пусть ref будет результатом вычисления AssignmentExpression.
- Пусть arg будет GetValue(ref).
- Вернуть список, единственным элементом которого будет arg.
Этот раздел также перечисляет случаи, когда список аргументов имеет 0 или более 1 аргумент.
Таким образом, всё передаётся по ссылке.
Доступ к свойствам объекта
Раздел 11.2.1 “Процессоры свойств”
Производство MemberExpression : MemberExpression [ Expression ] оценивается следующим образом:
- Пусть baseReference будет результатом вычисления MemberExpression.
- Пусть baseValue будет GetValue(baseReference).
- Пусть propertyNameReference будет результатом вычисления Expression.
- Пусть propertyNameValue будет GetValue(propertyNameReference).
- Вызовите CheckObjectCoercible(baseValue).
- Пусть propertyNameString будет ToString(propertyNameValue).
- Если синтаксическое производство, которое оценивается, содержится в коде строгого режима, пусть strict будет истинным, иначе пусть
strict будет ложным.- Вернуть значение типа Reference, чье базовое значение — это baseValue, а имя, на которое ссылается, — это propertyNameString, и флаг строгого режима — строгий.
Таким образом, свойства объектов всегда доступны по ссылке.
О ссылках
В разделе 8.7 “Спецификация типа ссылки” описано, что ссылки не являются реальными типами в языке — они используются только для описания поведения операторов delete, typeof и присвоения.
Определение “объекта”
В 5.1 версии определено, что “объект — это коллекция свойств”. Следовательно, мы можем сделать вывод, что значение объекта — это коллекция, но каково значение коллекции — плохо определено в спецификации и требует немного усилий для понимания.
Цитируя спрашивающего:
Примитивные типы (число, строка и т. д.) передаются по значению, но объекты […] могут передаваться как по значению (в этом случае мы считаем, что переменная, содержащая объект, на самом деле является ссылкой на объект) и по ссылке (когда мы считаем, что переменная на объект содержит сам объект).
Это неверно. Существует лишь один способ передачи объектов в качестве аргумента. Хотя также другие ответы делают различие между примитивными и непримитивными значениями, это отвлечение и не имеет значения.
Нет передачи по ссылке
Рассмотрите этот код, который не заботится о том, является ли a
намеренно примитивом или объектом:
function alter(arg) { /* какое-то магическое действие здесь с `arg` */ }
function main() {
var a, b;
a = b = { x: 1 }; // Пример значения. Это может быть объектом или примитивом: не имеет значения здесь
console.log(a === b); // true
alter(a);
console.log(a === b); // false? (невозможно)
}
Если бы функция alter
имела какой-то способ сделать второй вывод false
, тогда мы могли бы говорить о передаче по ссылке, как тогда arg
стал бы псевдонимом для a
. Но это просто невозможно в JavaScript. Концепция примитива не имеет значения: в JavaScript нет передачи по ссылке.
Объекты
В JavaScript объект — это ссылка на коллекцию свойств (и слотов). Когда функция вызывается с объектом в качестве аргумента, то это не создаёт новую коллекцию свойств. Переменная параметра функции (которая является локальной переменной) получит то же значение (т. е. ссылку на коллекцию свойств).
Присвоение переменной против мутации объекта
Структура данных вызывающего может быть изменена, также через вызов функции. Это очевидно верно только для изменяемых значений (по определению), и в JavaScript это происходит путём присвоения значения свойству. Необходимо различать две вещи:
- Присвоение свойству объекта изменяет объект, на который указывают как вызывающий, так и вызываемый.
- Присвоение переменной параметра не изменит данные вызывающего. В частности, если эта переменная параметра была объектом (ссылкой), то эта ссылка перезаписывается, и функция отделяется от данных вызывающего.
Ответ или решение
JavaScript является языком программирования, в котором значения передаются по значению (pass-by-value), но при этом важно понимать некоторые нюансы, связанные с разными типами данных, особенно с примитивами и объектами.
Примитивные типы
Примитивные типы в JavaScript включают такие значения, как number
, string
, boolean
, null
, undefined
и symbol
. Когда вы передаёте один из этих типов в функцию, JavaScript копирует его значение. Это означает, что изменения, внесённые в параметр функции, не влияют на оригинальное значение, переданное в функцию.
Пример:
function modifyPrimitive(value) {
value = value * 2;
console.log("Внутри функции:", value); // Вывод: Внутри функции: 20
}
let num = 10;
modifyPrimitive(num);
console.log("После функции:", num); // Вывод: После функции: 10
В этом примере переменная num
остаётся неизменной после вызова функции.
Ссылочные (объектные) типы
Объекты, включая массивы и функции, в JavaScript передаются по значению, но значение, которое передаётся, на самом деле является ссылкой на объект. Это означает, что если вы изменяете свойства объекта внутри функции, изменения будут отражены и за пределами этой функции, так как оба — ссылка на один и тот же объект.
Пример:
function modifyObject(obj) {
obj.property = "изменено";
console.log("Внутри функции:", obj.property); // Вывод: Внутри функции: изменено
}
let myObj = { property: "неизменено" };
modifyObject(myObj);
console.log("После функции:", myObj.property); // Вывод: После функции: изменено
В этом случае изменения в свойстве property
отражаются и в объекте myObj
.
Но что происходит, если мы присваиваем новую ссылку?
Если вы присваиваете новый объект внутри функции, это не повлияет на исходный объект. Это связано с тем, что сама ссылка передаётся по значению.
Пример:
function overwriteObject(obj) {
obj = { property: "новый объект" };
console.log("Внутри функции:", obj.property); // Вывод: Внутри функции: новый объект
}
let anotherObj = { property: "старый объект" };
overwriteObject(anotherObj);
console.log("После функции:", anotherObj.property); // Вывод: После функции: старый объект
Здесь в функции overwriteObject
новый объект лишь назначается локальной переменной obj
, что не влияет на оригинальный объект anotherObj
.
Итог
Таким образом, можно заключить, что:
- JavaScript всегда передаёт значения по значению (pass-by-value).
- Примитивные типы передаются как копии своих значений.
- Ссылочные типы (объекты) передаются как копии ссылок на сами объекты; изменения, сделанные внутри функции, могут влиять на свойства объекта, но замена самого объекта на новый не изменяет оригинал.
Спецификация JavaScript
Согласно спецификации ECMAScript, когда аргументы передаются в функции, значение аргументов передаётся в параметры. Аргументы не могут быть изменены в вызывающей среде, если вы изменяете значение параметра местной функции. Однако изменение свойств объекта через этот параметр будет видно и вне функции, так как вы работаете с одним и тем же объектом.
Надеюсь, это разъясняет вопрос о том, как JavaScript обращается с аргументами функций и предоставляет чёткое понимание передачи по значению и ссылке.