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 — Копирование объектов и ссылки