Frontend blog.

Reference type: Копирование объектов и ссылки

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

Примитивные типы: строки, числа, логические значения — присваиваются по «значению».

Например:

let message = 'Hello';
let phrase = message;

В результате мы имеем две независимые переменные, которые хранят строку «Привет».

Объекты ведут себя иначе. Переменная хранит в себе не сам объект, а «ссылку» на него.

Когда переменная объекта копируется — копируется ссылка на него, а сам объект не дублируется.

let user = {name: 'Maxim'};
let admin = user; // копируется ссылка
// Теперь у нас две переменные, которые
// которые содержат ссылку на один и тот же объект

// мы можем использовать любую из переменных
// для доступа к объекту и изменения его содержимого
admin.name = 'Иван';
console.log(user.name); // -> 'Иван'

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

Операторы равенства == и строгого равенства === для объектов работают одинаково.

Два объекта равны только в том случае, если это один и тот же объект

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

let a = {};
let b = a;

console.log(a == b); // -> true
console.log(a === b); // -> true

А теперь два разных объекта не равны, хоть и оба пустые:

let a = {};
let b = {}; // два независимых объекта

console.log(a == b); // -> false

Для сравнений типа ob1 > obj2 или для сравнений с примитивом obj == 5 объекты преобразуются в примитивы. Чаще всего такая операция — это ошибка программиста. Ниже несколько примеров приведения объектов к строке:

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

// вывод строки
alert(obj);

// используем объект в качестве имени свойства другого объекта
anotherObj[obj] = 123;

number Преобразование объекта к числу, в случае математических операций:

// явное преобразование
let num = Number(obj);

//  математическое (кроме бинарного "+")
let n = +obj; // унарный плюс

// при вычитании объектов Date резельтатом будет
// временной отрезок между датами
let delta = date1 - date2;

// сравнения больше/меньше
let greater = obj1 > obj2;

default Происходит редко, когда оператор «не уверен», какой тип ожидать

// бинарный плюс
let total = car1 + car2;

// obj = string/number
if (user == 1) {...}

Все объекты являются true в логическом контексте

Клонирование и объединение объектов, Object.assign

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

Пример:

let user = {
  name: 'Иван',
  age: 30,
};

let clone = {}; // создаем пустой объект

// скопируем все свойства объекта user в clone
for (let key in user) {
  clone[key] = user[key]
}

// теперь в переменной clone находится независимый клон user
clone.name = 'Николай'; // изменили данные

console.log(user.name); // -> 'Иван'
console.log(clone.name); // -> 'Николай'

А так же для этих целей можно использовать Object.assign.

Синтаксис:

Object.assign(dest, [src1, src2, src3...])
  • Первый аргумент dest — целевой объект.
  • Остальные аргументы src1, ..., srcN (может быть столько, сколько нужно) являются исходными объектами
  • Свойства всех перечисленных объектов начиная со второго, копируются в первый объект
  • Возвращается объект dest

Объединим несколько объектов в один:

let user = {name: 'Иван'};

let skills1 = {canRun: true};
let skills2 = {canFly: false};

// копируем все свойства skills1 и skills2 в user
Object.assign(user, skills1, skills2);

// теперь user = { name: 'Иван', canRun: true, canFly: false }

Если принимающий объект (user) уже имеет свойство с таким именем, оно будет перезаписано:

let user = {name: 'Иван'};

Object.assign(user, {name: 'Петр'});

console.log(user.name); // -> { name: 'Петр' }

А так же можно использовать Object.assign для замены for...in:

let user = {
  name: 'Иван',
  age: 25,
}

let clone = Object.assign({}, user);

console.log(clone); // -> { name: 'Иван', age: 25 }

Вложенное клонирование

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

let user = {
  name: "Иван",
  sizes: {
    height: 182,
    width: 50
  }
};

console.log(user.sizes.height); // -> 182

let clone = Object.assign({}, user);

console.log(user.sizes === clone.sizes); // -> true, один и тот же объект
// user и clone обращаются к одному sizes
user.sizes.width++; // меняем свойство в одном объекте
console.log(clone.sizes.width); // -> 51, видим результат в другом

Чтобы это исправить, нужно в цикле клонирования делать проверку, что свойство не является объектом. Если свойство является объектом, то нужно скопировать и его структуру тоже. Это называется «глубокое клонирование».

Для реализации глубокого клонирования можно использовать рекурсию. Или готовую реализацию — метод _.cloneDeep(obj) из JavaScript-библиотеки lodash.

Конспект статьи из учебника по JavaScriptКопирование объектов и ссылки

вернуться к списку