Является ли JavaScript языком передачи по ссылке или передачей по значению? - PullRequest
1259 голосов
/ 06 февраля 2009

Примитивные типы (Number, String и т. Д.) Передаются по значению, но объекты неизвестны, поскольку они могут быть оба переданы по значению (в случае, если мы считаем, что переменная, содержащая объект, на самом деле является ссылкой к объекту) и передается по ссылке (если учесть, что переменная объекта содержит сам объект).

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

Ответы [ 30 ]

1481 голосов
/ 03 сентября 2010

Рассмотрим этот пример:

function changeStuff(a, b, c)
{
  a = a * 10;
  b.item = "changed";
  c = {item: "changed as well"};
}

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
  • Если бы JavaScript передал копию всех значений / объектов, то изменение obj1.item не повлияло бы на obj1 вне функции, потому что мы бы изменили копию объекта .
  • Если бы это было по ссылке, то все бы изменилось. num будет 100, а obj2.item будет читать "changed as well".

Так что же происходит?
(Обратите внимание, что мы еще не упомянули термин pass-by-value и это было специально).

Во-первых, нам нужна дополнительная концепция: типы значений и ссылочные типы.

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

Давайте на секунду забудем о функциях, чтобы поговорить о ссылочных типах:

var obj1 = {item: "unchanged"};
var obj2 = obj1;

console.log(obj1.item);
console.log(obj2.item);

obj2.item = "changed";

console.log(obj1.item);
console.log(obj2.item);

Как и следовало ожидать, вы получите результат:

unchanged
unchanged
changed
changed

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

Присвоение переменной obj1 переменной obj2 не копирует и не копирует объект. В противном случае изменение obj2.item изменило бы только скопированный объект, а не оригинал, и мы бы увидели другой вывод.

Вы, наверное, уже знали это, но это , потому что --- в JavaScript --- объекты являются "ссылочными типами". Это означает, что переменная хранит не сам объект , а ссылку на нашего объекта.

С типами значений это могло бы произойти:

var obj1 = {item: "unchanged"};
var obj2 = obj1; // we would now have two objects

obj2.item = "changed"; // change/mutate the copy

console.log(obj1.item); // would print "unchanged"
console.log(obj2.item); // would print "changed"

Но, как вы знаете, этого не происходит в JavaScript.

Возвращаясь к JavaScript, в obj2 = obj1; мы что-то копируем: копируем то, что хранит переменная. Что хранит переменная? «Ссылка» на объект.

Так, как это относится к передаче по значению против передачи по ссылке?

При передаче по значению происходит следующее:

Каждый раз, когда мы вызываем функцию, для каждого параметра создается новая переменная, и тогда все, что сохраняется в передаваемых вами переменных, будет копироваться и сохраняться в переменных, созданных для вызова функции. Итак, в передаче по значению бит «значение» означает: мы передаем «значение» внутри переменной.

Давайте посмотрим, что это значит, когда мы используем объекты так, как они ведут себя в JavaScript, с учетом этого псевдокода:

function changeStuff(b, c)
{
  ...
}

var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
var obj3 = obj2;

changeStuff(obj1, obj2);

console.log(obj1.item);    
console.log(obj2.item);
console.log(obj3.item);

Сначала мы создаем два объекта, obj1 относится к одному, obj2 относится ко второму. Затем мы создаем третью переменную obj3, которая также относится ко второму объекту. (Можно сказать, что obj3 содержит копию ссылки на второй объект, который хранится в obj2).

Мы вызываем функцию. Потому что мы используем передачу по значению, создаются новые переменные b и c, затем все, что хранится в переменной obj1, копируется в b, а все, что хранится в переменной obj2, копируется в c.

Если вы все еще отслеживаете:

  • obj1 и b теперь содержат одно и то же (ссылка на первый объект)
  • obj2, obj3 и c содержат одно и то же (ссылка на второй объект)

Функция выполняется, после чего мы печатаем вещи.

Давайте подумаем о том, что может произойти, если наша функция никогда не использует переменные obj1/2/3, где мы написали ....

  • Мы знаем, что теперь у функции есть способы доступа к нашему первому и второму объектам, а именно через ссылки на эти объекты, хранящиеся в b и c соответственно.

    Таким образом, функция может изменять эти два объекта по своему усмотрению.

  • Мы также знаем, что присвоение самой переменной просто меняет то, что хранится внутри этой переменной. Так что changeStuff может также заставить переменные b и c ссылаться на объекты, отличные от того, что они делают в настоящее время, но это не повлияет на наши собственные переменные obj1/2/3

Итак, как только функция будет выполнена и мы запустим наши операторы журнала, мы знаем , что obj1 будет по-прежнему ссылаться на первый созданный нами объект, а obj2 и obj3 будут по-прежнему ссылаясь на второй объект, который мы создали.

console.log(obj1.item);
console.log(obj2.item);
console.log(obj3.item);

Теперь мы знаем, что первая строка может печатать что угодно: changeStuff имеет ссылку на наш первый объект и может изменить свойство item этого объекта. А поскольку obj1 по-прежнему ссылается на первый объект, мы напечатаем измененное свойство item.

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

Для третьей строки мы знаем, что obj3 относится к тому же объекту, что и obj2. Следовательно, он должен печатать точно так же, как и в предыдущей строке, так как мы печатаем одно и то же , мы просто используем разные способы, чтобы добраться туда.


Что если бы наш гипотетический язык использовал вместо этого ссылку на ссылку?

Когда мы вызываем функцию, мы больше не передаем (копируем) содержимое переменных. (Помните, содержимое - это ссылки на объекты). Вместо этого мы будем говорить функции: пожалуйста, используйте мои переменные obj1 и obj2 всякий раз, когда вы видите, что b или c используется в вашем коде.

Что это значит? Это означает, что когда функция присваивается b, она фактически присваивает наш obj1 и, соответственно, присваивает obj2, когда она присваивает c. Таким образом, после завершения функции obj3 гарантированно будет по-прежнему ссылаться на наш второй объект, но obj2 можно было бы переназначить любому другому или совершенно новому объекту. Мы понятия не имеем.

Или другими словами: при передаче по ссылке переменные параметров в функциях - это не новые независимые переменные, а "псевдонимы" для переменных вызывающего.

Таким образом, все строки console.log теперь могут печатать что-то совершенно другое!


Так что же делает JavaScript ? Давайте вспомним наш оригинальный пример в верхней части этого поста. Функция назначена на a. Однако num не изменилось. Мы также присвоили c, но не увидели влияния этого назначения на obj2. Так что JavaScript не использовал переход по ссылке .

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

Итак: JavaScript передается по значению

Заключительные мысли : Помните, как при передаче по ссылке мы даже не смотрим на то, что находится внутри переменной? Мы просто говорим «использовать эту переменную» или «использовать эту другую переменную здесь»? В этих предложениях мы ссылаемся на на переменную. Этот, этот другой. Поэтому, когда вы читаете слово «ссылка» в «передача по ссылке», вы должны думать о , ссылающемся на переменные или ссылки на переменные . Неважно, что внутри переменной.

С другой стороны, «передача по значению» говорит о передаче / копировании того, что на самом деле хранится внутри самой переменной, в новые независимые переменные функции. Таким образом, значение в этом термине говорит о том, что находится внутри переменной. Неважно, что это такое, но так как мы создаем новую переменную, нам нужно «заполнить» ее чем-нибудь.

Тогда у нас есть «ссылочные типы» и «типы значений». Они говорят о том, что на самом деле хранится внутри переменной. «Значение» внутри переменной. С «ссылочными типами» наши вещи не хранятся внутри переменной напрямую. Вместо этого переменная хранит ссылку на нашу вещь . Таким образом, «значение» в переменной является ссылкой на вещь. В JavaScript «вещи» являются объектами. С типами значений вещи хранятся непосредственно внутри переменной. Таким образом, «значение» внутри нашей переменной на самом деле является нашей вещью. Эти типы вещей сохраняются в качестве значения в переменной. Отсюда и «типы значений».

Как только вы поймете, что слова "ссылка" и "значение" могут означать очень разные вещи, в зависимости от того, где они используются в предложении / термине, становится намного проще.

Вот как мы добираемся до печально запутанного: «передача по значению, где значения являются ссылками», которую используют некоторые люди. Он точно описывает, что делает JavaScript. Черт возьми, он также описывает Python, Ruby или Java 2 (речь идет только об объектах). Но это на самом деле ничего не объясняет .

Например, вы, вероятно, согласитесь с тем, что выражение «передача ссылки» звучит очень похоже на «передачу по ссылке». За исключением того, что слово «ссылка» в «передаче ссылки» понимается как «ссылка на некоторый объект» 1 , но в «передаче по ссылке» это не означает этого. Неудивительно, что многие заканчивают тем, что изучают / верят, что некоторые языки используют «переход по ссылке», если такой язык используется, даже если язык не ошибается ... В конце концов, кто бы мог догадаться, что это не так? просто различие без разницы, если вы уже не знаете различие?

Запутывают ли термины? Да! Определенно! Сказать, однако, что JavaScript не выполняет «передачу по значению» и сказать, что он делает «что-то другое» или даже пойти так далеко, чтобы сказать: «давайте просто назовем эту« передачу по ссылке »», медвежью услугу людям. Какая польза от того, что «JavaScript не использует передачу по значению», если нет другого поведения, чтобы сопоставить его? Нет необходимости использовать термины «передача по значению» или «передача по ссылке» или что-то еще при обучении JavaScript. JavaScript делает то, что делает JavaScript.

Примечание: в C # land вы можете определять и создавать объекты, которые являются ссылочными типами, вы можете определять и создавать объекты, которые являются типами значений. Вы можете создавать методы, которые принимают ваши ссылочные типы, используя передачу по значению, или вы можете захотеть использовать передачу по ссылке, по вашему выбору. Вы можете создавать методы, которые принимают ваши типы значений и используют передачу по значению, или вы можете использовать передачу по ссылке. Вы получаете полный спектр комбинаций. В JavaScript вы ничего не можете выбрать. Наконец, что не менее важно, вы можете сделать этот выбор для каждого параметра независимо.

Иногда люди называют поведение, которое использует JavaScript call-by-share .

Просто помните, obj1 и obj2 не являются объектами. Они переменные. Переменные ссылаются на объекты. Несмотря на то, что в разговорной речи мы могли бы сказать, давайте изменим item из obj1 на obj1.item = "something else" или подытожим, что как "мы изменили obj1", всегда неявно понимается, что мы не изменили буквально obj1 (что переменная), но мы изменили объект , на который он ссылается. Использование языка, такого как «Когда мы изменяем объект внутри функции с использованием b.item = "changed", это изменение распространяется в вызывающую сторону» также является плохой идеей: распространения изменения не происходит. Нам нужно начать объяснение до модификации: b и obj1 - это два способа доступа к к одному и тому же объекту . Изменение указанного объекта делает именно это. Какой путь мы используем для доступа к этому объекту , не имеет значения.

1 Таким образом, слово "ссылка" означает , ссылаясь на "ссылки на объекты":)

2 действительно с июня 2019 года. Java добавит поддержку пользовательских типов значений в некоторый момент в будущем

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

418 голосов
/ 15 марта 2011

Он всегда передается по значению, но для объектов значение переменной является ссылкой. Из-за этого, когда вы передаете объект и изменяете его members , эти изменения сохраняются вне функции. Это выглядит как передача по ссылке. Но если вы на самом деле измените значение переменной объекта, вы увидите, что это изменение не сохраняется, доказывая, что оно действительно передается по значению.

Пример:

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); /* change did not persist */

console.log("before changeMember: " + x.member);
changeMember(x);
console.log("after changeMember: " + x.member); /* change persists */

Выход:

before changeObject: foo
in changeObject: bar
after changeObject: foo

before changeMember: foo
in changeMember: bar
after changeMember: bar
139 голосов
/ 06 февраля 2009

Переменная не «держит» объект, она содержит ссылку. Вы можете присвоить эту ссылку другой переменной, теперь обе ссылаются на один и тот же объект. Он всегда передается по значению (даже если это значение является ссылкой ...).

Нет способа изменить значение, хранящееся в переменной, передаваемой в качестве параметра, что было бы возможно, если бы JS поддерживал передачу по ссылке.

90 голосов
/ 04 августа 2014

Мои 2 цента .... Я так понимаю. (Не стесняйтесь поправлять меня, если я ошибаюсь)

Пришло время выбросить все, что вы знаете о передаче по значению / ссылке.

Потому что в JavaScript не имеет значения, передается ли он по значению, по ссылке или как угодно. Важна мутация против присвоения параметров, переданных в функцию.

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

var object1 = {};
var object2 = {};

Что мы сделали, так это «назначение» ... Мы присвоили 2 отдельных пустых объекта переменным «object1» и «object2».

Теперь предположим, что нам нравится object1 лучше ... Итак, мы «присваиваем» новую переменную.

var favoriteObject = object1;

Затем, по какой-то причине, мы решили, что нам нравится объект 2 лучше. Итак, мы просто делаем небольшое переназначение.

favoriteObject = object2;

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

object2.name = 'Fred';
console.log(favoriteObject.name) // logs Fred
favoriteObject.name = 'Joe';
console.log(object2.name); // logs Joe 

Хорошо, теперь давайте посмотрим на примитивы как строки, например

var string1 = 'Hello world';
var string2 = 'Goodbye world';

Опять мы выбираем любимую.

var favoriteString = string1;

Обе переменные favouriteString и string1 присвоены «Hello world». А что если мы захотим изменить нашу любимую строку ??? Что будет ??? 1029 *

favoriteString = 'Hello everyone';
console.log(favoriteString); // Logs 'Hello everyone'
console.log(string1); // Logs 'Hello world'

Э-э-э ... Что случилось. Мы не могли изменить string1, изменив favourString ... Почему ?? Потому что мы не изменили нашу строку объект . Все, что мы сделали, это "RE ASSIGN" переменную favourString для новой строки. Это по существу отключило его от string1. В предыдущем примере, когда мы переименовали наш объект, мы ничего не назначали. (Ну, не для самой переменной , ... мы, однако, присвоили свойство name новой строке.) Вместо этого мы просто мутировали объект, который сохраняет связи между двумя переменными и базовые объекты. (Даже если бы мы хотели изменить или изменить строковый объект сам , мы не смогли бы этого сделать, потому что строки фактически неизменны в JavaScript)

Теперь, что касается функций и передачи параметров .... Когда вы вызываете функцию и передаете параметр, то, что вы по сути делаете, это «присваивание» новой переменной, и оно работает точно так же, как если бы вы просто назначается с помощью знака равенства (=).

Возьмите эти примеры.

var myString = 'hello';

// Assign to a new variable (just like when you pass to a function)
var param1 = myString; 
param1 = 'world'; // Re assignment

console.log(myString); // logs 'hello'
console.log(param1);   // logs 'world'

Теперь тоже самое, но с функцией

function myFunc(param1) {
    param1 = 'world';

    console.log(param1);   // logs 'world'
}

var myString = 'hello';
// Calls myFunc and assigns param1 to myString just like param1 = myString
myFunc(myString); 

console.log(myString); // logs 'hello'

Хорошо, теперь давайте приведем несколько примеров использования объектов вместо ... во-первых, без функции.

var myObject = {
    firstName: 'Joe',
    lastName: 'Smith'
};

// Assign to a new variable (just like when you pass to a function)
var otherObj = myObject;

// Let's mutate our object
otherObj.firstName = 'Sue'; // I guess Joe decided to be a girl

console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Sue'

// Now, let's reassign the variable
otherObj = {
    firstName: 'Jack',
    lastName: 'Frost'
};

// Now, otherObj and myObject are assigned to 2 very different objects
// And mutating one object has no influence on the other
console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Jack';

Теперь то же самое, но с вызовом функции

function myFunc(otherObj) {

    // Let's mutate our object
    otherObj.firstName = 'Sue';
    console.log(otherObj.firstName); // Logs 'Sue'

    // Now let's re-assign
    otherObj = {
        firstName: 'Jack',
        lastName: 'Frost'
    };
    console.log(otherObj.firstName); // Logs 'Jack'

    // Again, otherObj and myObject are assigned to 2 very different objects
    // And mutating one object doesn't magically mutate the other
}

var myObject = {
    firstName: 'Joe',
    lastName: 'Smith'
};

// Calls myFunc and assigns otherObj to myObject just like otherObj = myObject
myFunc(myObject);

console.log(myObject.firstName); // Logs 'Sue', just like before

Хорошо, если вы прочитали весь этот пост, возможно, теперь вы лучше понимаете, как работают вызовы функций в JavaScript. Неважно, передается ли что-то по ссылке или по значению ... Что важно, так это присвоение или мутация.

Каждый раз, когда вы передаете переменную в функцию, вы «присваиваете» имя переменной параметра, как если бы вы использовали знак равенства (=).

Всегда помните, что знак равенства (=) означает присваивание. Всегда помните, что передача параметра в функцию в JavaScript также означает присваивание. Они одинаковы, и эти 2 переменные связаны одинаково (то есть они не связаны, если не считать, что они назначены одному и тому же объекту).

Единственный раз, когда «изменение переменной» влияет на другую переменную, - это когда мутирует базовый объект (в этом случае вы изменяете не переменную, а сам объект.

Нет никакого смысла проводить различие между объектами и примитивами, потому что он работает точно так же, как если бы у вас не было функции, а просто использовали знак равенства для присвоения новой переменной.

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

function myFunc(myString) {
    // myString is private and does not affect the outer variable
    myString = 'hello';
}

var myString = 'test';
myString = myString; // Does nothing, myString is still 'test';

myFunc(myString);
console.log(myString); // logs 'test'
64 голосов
/ 26 сентября 2015

Рассмотрим следующее:

  1. Переменные указывают на значения в памяти.
  2. Переназначение переменной просто указывает этому указателю на новое значение.
  3. Переназначение переменной никогда не повлияет на другие переменные, которые указывали на тот же объект

Итак, забудьте о "передача по ссылке / значению" не зацикливайтесь на "передаче по ссылке / значению", потому что:

  1. Термины используются только для описания поведения языка, но не обязательно фактической базовой реализации. В результате этой абстракции критические детали, которые важны для достойного объяснения, теряются, что неизбежно приводит к текущей ситуации, когда один термин не адекватно описывает реальное поведение, и необходимо предоставить дополнительную информацию
  2. Эти понятия изначально не были определены с целью описания javascript в частности, и поэтому я не чувствую необходимости использовать их, когда они только добавляют путаницы.

Чтобы ответить на ваш вопрос: указатели пропущены.


// code
var obj = {
    name: 'Fred',
    num: 1
};

// illustration
               'Fred'
              /
             /
(obj) ---- {}
             \
              \
               1


// code
obj.name = 'George';


// illustration
                 'Fred'


(obj) ---- {} ----- 'George'
             \
              \
               1


// code
obj = {};

// illustration
                 'Fred'


(obj)      {} ----- 'George'
  |          \
  |           \
 { }            1


// code
var obj = {
    text: 'Hello world!'
};

/* function parameters get their own pointer to 
 * the arguments that are passed in, just like any other variable */
someFunc(obj);


// illustration
(caller scope)        (someFunc scope)
           \             /
            \           /
             \         /
              \       /
               \     /
                 { }
                  |
                  |
                  |
            'Hello world'

Некоторые заключительные комментарии:

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


var a = [1,2];
var b = a;

a = [];
console.log(b); // [1,2]
// doesn't work because `b` is still pointing at the original array
23 голосов
/ 03 июня 2011

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

19 голосов
/ 13 февраля 2015

Думайте об этом так: он всегда передается по значению. Однако значение объекта - это не сам объект, а ссылка на этот объект.

Вот пример, передавая число (примитивный тип)

function changePrimitive(val) {
    // At this point there are two '10's in memory.
    // Changing one won't affect the other
    val = val * 10;
}
var x = 10;
changePrimitive(x);
// x === 10

Повторение этого с объектом дает разные результаты:

function changeObject(obj) {
    // At this point there are two references (x and obj) in memory,
    // but these both point to the same object.
    // changing the object will change the underlying object that
    // x and obj both hold a reference to.
    obj.val = obj.val * 10;
}
var x = { val: 10 };
changeObject(x);
// x === { val: 100 }

Еще один пример:

function changeObject(obj) {
    // Again there are two references (x and obj) in memory,
    // these both point to the same object.
    // now we create a completely new object and assign it.
    // obj's reference now points to the new object.
    // x's reference doesn't change.
    obj = { val: 100 };
}
var x = { val: 10 };
changeObject(x);
// x === { val: 10}
14 голосов
/ 06 февраля 2009

Javascript всегда передача по значению , все имеет тип значения. Объекты являются значениями, функции-члены объектов сами являются значениями (помните, что функции являются первоклассными объектами в Javascript). Кроме того, что касается концепции, что все в Javascript является объектом , это неправильно. Строки, символы, числа, логические значения, значения NULL и неопределенные значения являются примитивами . Иногда они могут использовать некоторые функции-члены и свойства, унаследованные от своих базовых прототипов, но это только для удобства, это не означает, что они сами являются объектами. Попробуйте следующее для справки

x = "test";
alert(x.foo);
x.foo = 12;
alert(x.foo);

В обоих оповещениях вы найдете значение неопределенным.

12 голосов
/ 30 декабря 2015

В JavaScript тип значения only определяет, будет ли это значение присваиваться value-copy или reference-copy .

Примитивные значения всегда присваиваются / передаются копией значения :

  • null
  • undefined
  • строка
  • булево
  • символ в ES6

Составные значения всегда присваиваются / передаются по эталонной копии

  • объекты
  • Массивы
  • функция

Например

var a = 2;
var b = a; // `b` is always a copy of the value in `a`
b++;
a; // 2
b; // 3

var c = [1,2,3];
var d = c; // `d` is a reference to the shared `[1,2,3]` value
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 more не «владеют» значением [1,2,3] - оба являются равными равноправными ссылками на значение. Таким образом, при использовании любой ссылки для изменения (.push(4)) самого фактического общего значения array оно влияет только на одно общее значение, и обе ссылки будут ссылаться на вновь измененное значение [1,2,3,4].

var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]

// later
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]

Когда мы делаем назначение b = [4,5,6], мы абсолютно ничего не делаем, чтобы повлиять на то, где a все еще ссылается ([1,2,3]). Для этого b должен быть указателем на a, а не ссылкой на array - но в JS такой возможности не существует!

function foo(x) {
    x.push( 4 );
    x; // [1,2,3,4]

    // later
    x = [4,5,6];
    x.push( 7 );
    x; // [4,5,6,7]
}

var a = [1,2,3];

foo( a );

a; // [1,2,3,4]  not  [4,5,6,7]

Когда мы передаем аргумент a, он присваивает копию ссылки a на x. x и a - это отдельные ссылки, указывающие на одно и то же значение [1,2,3]. Теперь внутри функции мы можем использовать эту ссылку для изменения самого значения (push(4)). Но когда мы делаем присвоение x = [4,5,6], это никоим образом не влияет на то, куда указывает начальная ссылка a - все еще указывает на (теперь измененное) значение [1,2,3,4].

Чтобы эффективно передать составное значение (например, array) по значению-копированию, вам необходимо вручную сделать его копию, чтобы переданная ссылка по-прежнему не указывала на оригинал. Например:

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.

Источник

11 голосов
/ 13 мая 2011

Очень подробное объяснение о копировании, передаче и сравнении по значению и по ссылке содержится в этой главе книги "JavaScript: полное руководство".

Прежде чем покинуть тему манипулирование объектами и массивами ссылка, нам нужно прояснить точку номенклатуры. Фраза "пройти мимо" Ссылка "может иметь несколько значений. Для некоторых читателей эта фраза относится к метод вызова функций, который позволяет функции назначать новые значения к его аргументам и иметь те измененные значения видны за пределами функция. Это не так, как термин используется в этой книге. Здесь мы имеем в виду просто что ссылка на объект или массив - не сам объект - передается в функцию. Функция можно использовать ссылку для изменения свойства объекта или элементов массива. Но если функция перезаписывает ссылку с ссылка на новый объект или массив, эта модификация не видна вне функции. Читатели знакомы с другим значением этот термин может предпочесть сказать, что объекты и массивы передаются значение, но значение, которое передается на самом деле ссылка, а не сам объект.

Одна вещь, которую я до сих пор не могу понять. Проверьте код ниже. Есть мысли?

function A() {}
A.prototype.foo = function() {
    return 'initial value';
}


function B() {}
B.prototype.bar = A.prototype.foo;

console.log(A.prototype.foo()); //initial value
console.log(B.prototype.bar()); //initial value

A.prototype.foo = function() {
    return 'changed now';
}

console.log(A.prototype.foo()); //changed now
console.log(B.prototype.bar()); //Why still 'initial value'???
...