Передача по ссылке или по значению. Spread оператор (ES6 - ES9)

Передача по ссылке и по значению


Имеем две переменных, первая имеет конкретное значение, вторая принимает значение от первой:

Код

let first = 5;
let second = first;  

Сейчас значением обоих переменных будет число 5. Переопределим значение второй переменной, присвоим ей значение 10:

Код

let first = 5;
let second = first;

second = 10;

console.log(first);
console.log(second);

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

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
5
10

Рассмотрим теперь две переменные, в первой из которых будет уже объект, а вторая также принимает значение первой:

Код

let first = {
  a: 5,
  b: 10
};

let second = first;

Сейчас в переменной second у нас, как мы считаем, лежит копия объекта из переменной first. Давайте попробуем изменить значение свойства a у объекта в переменной second и выведем в консоль значения обоих переменных:

Код

let first = {
  a: 5,
  b: 10
};

let second = first;

second.a = 10;

console.log(first);
console.log(second);

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
{ a: 10, b: 10 }
{ a: 10, b: 10 }
Мы видим, что значение свойства a поменялось не только в объекте переменной second, но и в объекте переменной first.
Получается, что изменяя копию, мы изменяем на самом деле, начальный объект. Так происходит потому, что работая с объектами, мы работаем со ссылкой на первоначальный объект, а не с его копией.
То есть в данном случае:

Код

let second = first;

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

Создание поверхностных копий объектов


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

Вариант 1: использование цикла

Смысл здесь такой: задействуем функцию, которая будет перебирать имеющийся объект и на основании его свойств создаст новый объект (копию исходного).

Код

function copy(obj) {
  let newObj = {};
  let key;
  for(key in obj) {
  newObj[key] = obj[key];
  }

  return newObj;
}

В качестве аргумента наша функция будет принимать какой-то исходный объект (мы его будем передавать при вызове функции).
Внутри функции в переменной newObj изначально мы положим пустой объект, который будет потом наполняться в результате перебора циклом for in исходного объекта.
Смысл работы цикла: взять каждое из свойств входящего объекта и присвоить его как свойство для нового объекта. Таким образом мы и сформируем независимую копию исходного объекта, которую нам вернёт функция в результате своей работы.

Давайте напишем наш исходный объект:

Код

const original = {
  a: 5,
  b: 10,
  c: {
  d: 4,
  e: 6
  }
};


Вызовем нашу функцию, передав ей в качестве аргумента переменную original, содержащую объект, а результат сохраним в новую переменную copyOriginal. И вот теперь давайте также обратимся к свойству а нашей копии объекта и присвоим ей новое значение, допустим, число 10 :

Код

function copy(obj) {
  let newObj = {};
  let key;
  for(key in obj) {
  newObj[key] = obj[key];
  }

  return newObj;
}

const original = {
  a: 5,
  b: 10,
  c: {
  d: 4,
  e: 6
  }
};

let copyOriginal = copy(original);

copyOriginal.a = 10;

console.log(original);
console.log(copyOriginal);

Выводим в консоль содержимое наших обоих объектов - исходного и его клона (копии в переменной copyOriginal) и видим, что изменения в копии не затронули исходного объекта:

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
{ a: 5, b: 10, c: { d: 4, e: 6 } }
{ a: 10, b: 10, c: { d: 4, e: 6 } }

Но, если мы обратимся к свойству d нашей копии, которое находится внутри вложенного объекта, и присвоим ему новое значение, то это также приведёт к изменениям в объекте исходнике

Код

function copy(obj) {
  let newObj = {};
  let key;
  for(key in obj) {
  newObj[key] = obj[key];
  }

  return newObj;
}

const original = {
  a: 5,
  b: 10,
  c: {
  d: 4,
  e: 6
  }
};

const copyOriginal = copy(original);

copyOriginal.c.d = 10;

console.log(original);
console.log(copyOriginal);

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
{ a: 5, b: 10, c: { d: 10, e: 6 } }
{ a: 5, b: 10, c: { d: 10, e: 6 } }

Это произошло потому, что мы работаем с так называемой поверхностной копией, для которой характерно, что копия создаётся для свойств, лежащих на первом уровне, а вложенная структура(в нашем случае это вложенный объект) будет иметь, опять же, ссылочный тип данных.

Вариант 2. Использование метода Object.assign()

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

Код

const firstObj = {
  a: 5,
  b: 4,
  c: 20,
  d: {
  e: 2,
  f: true
  }
};

const secondObj = {
  x: 12,
  y: false
};

Чтобы объединить эти два объекта и получить на выходе конечный объект, используем метод Object.assign(), где первым аргументом будет тот объект, в который добавляется, а вторым аргументом будет тот, который добавляется.

Добавим меньший объект в больший и результат выведем в консоль:

Код

const firstObj = {
  a: 5,
  b: 4,
  c: 20,
  d: {
  e: 2,
  f: true
  }
};

const secondObj = {
  x: 12,
  y: false
};

console.log(Object.assign(firstObj,secondObj));

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
{ a: 5, b: 4, c: 20, d: { e: 2, f: true }, x: 12, y: false }

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

Например, есть объект original:

Код

const original = {
  a: 5,
  b: 4,
  c: 20,
  d: {
  e: 2,
  f: true
  }
};

создадим новую переменную, назовём её clone, и поместим в неё результат добавления нашего объекта original в пустой объект, в консоль выведем содержимое переменной clone:

Код

const original = {
  a: 5,
  b: 4,
  c: 20,
  d: {
  e: 2,
  f: true
  }
};

const clone = Object.assign({}, original);
console.log(clone);

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
{ a: 5, b: 4, c: 20, d: { e: 2, f: true } }

Как видим, мы получили копию исходного объекта original и если мы в ней сейчас отредактируем свойства, например, присвоим новые значения свойствам b и f:

Код

const original = {
  a: 5,
  b: 4,
  c: 20,
  d: {
  e: 2,
  f: true
  }
};

const clone = Object.assign({}, original);

clone.b = 0;
clone.d.f = false;

console.log(clone);
console.log(original);

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

Код

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
{ a: 5, b: 0, c: 20, d: { e: 2, f: false } }
{ a: 5, b: 4, c: 20, d: { e: 2, f: false } }

Вариант 3. Создание копии массива. Метод slice()

Как частный случай объектов, рассмотрим, как можно создать копию массива. Допустим, у нас есть какой-то массив:

Код

const arr = ['Николай', 'Сергей', 'Виталий'];

Если мы сейчас создадим новую переменную newArr и присвоим ей значение arr:

Код

const newArr = arr;

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

Код

const arr = ['Николай', 'Сергей', 'Виталий'];

const newArr = arr;

newArr[0] = 'Алексей';

console.log(arr);
console.log(newArr);

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
[ 'Алексей', 'Сергей', 'Виталий' ]
[ 'Алексей', 'Сергей', 'Виталий' ]

Как видите, изменения коснулись также исходного массива.

Правильно создавать копию массива можно с помощью метода slice():

Код

const arr = ['Николай', 'Сергей', 'Виталий'];

const newArr = arr.slice();

newArr[0] = 'Алексей';

console.log(arr);
console.log(newArr);

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
[ 'Николай', 'Сергей', 'Виталий' ]
[ 'Алексей', 'Сергей', 'Виталий' ]

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

Код

const arr = ['Николай', 'Сергей', 'Виталий', [1, 2, 3]];

const newArr = arr.slice();

newArr[3][0] = 'Привет!';

console.log(arr);
console.log(newArr);

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
[ 'Николай', 'Сергей', 'Виталий', [ 'Привет!', 2, 3 ] ]
[ 'Николай', 'Сергей', 'Виталий', [ 'Привет!', 2, 3 ] ]


Стандарты ES6-ES9. Оператор разворота (spread оператор)


Как работает spread оператор? Разберём на простом примере: у нас есть три массива, сохранённые в разных переменных.
Два массива будут иметь какие-то данные, а третий пока пустой. В него мы будем добавлять данные из тех двух массивов.

Код

const arrOne = ['Николай', 'Сергей', 'Виталий'];
const arrTwo = ['Наталья','Ольга','Софья'];
const arrSumm = [ ];

Как добавить данные из первых двух массивов в пустой массив arrSumm? С помощью spread оператора сделать это довольно просто, нужно всего лишь перечислить названия переменных через запятую, при этом добавив перед ними spread оператор (три точки):

Код

const arrOne = ['Николай', 'Сергей', 'Виталий'];
const arrTwo = ['Наталья','Ольга','Софья'];
const arrSumm = [...arrOne, ...arrTwo];

console.log(arrSumm);

Смотрим содержимое бывшего пустого массива в консоли:

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
[ 'Николай', 'Сергей', 'Виталий', 'Наталья', 'Ольга', 'Софья' ]

Оператор разворота просто развернул структуры двух первых массивов на отдельные элементы, которые "высыпались" в пустой массив arrSumm.

Рассмотрим еще один пример. Есть функция с тремя аргументами и массив с данными:

Код

function calc(a,b,c) {
  console.log(a);
  console.log(b);
  console.log(c);
}

const arr = [1,2,3];

Нам нужно данные из массива передать как аргументы функции. Сам по себе массив в качестве аргумента быть не может, и тогда нам поможет spread оператор:

Код

function calc(a,b,c) {
  console.log(a);
  console.log(b);
  console.log(c);
}

const arr = [1,2,3];

calc(...arr);

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

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
1
2
3


Создание поверхностной копии массива с помощью spread оператора

Начальный массив:

Код

const arr = [1,2,3,4,5];

создаём поверхностную копию и сохраням её в новую переменную newArr, переопределяем значение для первого элемента массива-копии и выводим содержимое обоих массивов в консоль:

Код

const arr = [1,2,3,4,5];

const newArr = [...arr];
newArr[0] = 0;

console.log(newArr);
console.log(arr);

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
[ 0, 2, 3, 4, 5 ]
[ 1, 2, 3, 4, 5 ]


Создание поверхностной копии объекта с помощью spread оператора

Имеем начальный объект:

Код

const obj = {
  a: 5,
  b: 10,
  c: {
  x: 3,
  y: 5
  }
};

Создаём копию исходного объекта и помещаем её в новую переменную newObj. Переопределяем значения свойств а и х объекта-копии и смотрим результаты в консоли

Код

const obj = {
  a: 5,
  b: 10,
  c: {
  x: 3,
  y: 5
  }
};

const newObj = {...obj};

newObj.a = 10;
newObj.c.x = 5;

console.log(obj);
console.log(newObj);

Цитата

[Running] node "c:\Users\psstu\Desktop\jstemp\tempCodeRunnerFile.js"
{ a: 5, b: 10, c: { x: 5, y: 5 } }
{ a: 10, b: 10, c: { x: 5, y: 5 } }

Результат аналогичен всем предыдущим результатам для поверхностных копий объектов: свойства первого уровня при редактировании объекта-клона в исходном объекте остались неизменными, свойства же вложенной конструкции (в данном случае объекта внутри объекта) изменились как в объекте-клоне, так и в исходном объекте, поскольку в объекте-клоне сохранена не структура вложенного объекта, а ссылка на исходный объект.

Комментарии к материалу: