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

Привет, Хабр! Меня зовут Александр Дудукало, я автор базового курса по JavaScript.  В этой статье я простыми словами расскажу, как работают ссылки, почему это важно знать и как правильно копировать объекты.

Важно: некоторые технические детали сознательно упрощены для лучшего понимания основ. Если вам удобнее изучать материал в видеоформате — смотрите ролик

Ссылочный тип данных

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

const product1 = {
 name: "Кофе",
 price: 400,
};

const product2 = product1;

product2.price = 900;

// Товар 1
console.log(Стоимость ${product1.name}: ${product1.price});

// Товар 2
console.log(Стоимость ${product2.name}: ${product2.price});
Результат в консоли.
Результат в консоли.


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

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

let price1 = 400;
let price2 = price1;

price2 = 900;

console.log(price1);
console.log(price2);
Результат в консоли.
Результат в консоли.

Видите разницу? В первом случае изменение product2 задело product1, а во втором изменение price2 никак не повлияло на price1. В этом и заключается ключевое различие. А теперь перейдем к определению.

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

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

В показанном выше примере в константу product2 копируется ссылка на объект, а именно на тот же объект, который был создан при объявлении константы product1. То есть обе константы ссылаются на одни и те же данные. 

Для ясности предлагаю посмотреть на схему:

Один объект для двух констант.
Один объект для двух констант.

Примитивные типы данных — это «простые» типы, которые хранят непосредственно свое значение. При копировании создается независимая копия этого значения.

К ним относятся:

  • number (числа) — 5, -10.5;

  • string (строки) — «Привет»;

  • boolean (логический тип) — true или false;

  • null — специальное значение «ничего»;

  • undefined — значение «не определено»;

  • symbol и bigint (используются реже).

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

Бесплатный базовый курс по JS

Рассказываем, как работать с переменными, типами данных, функциями и многом другом!

Начать изучение →

Копирование объектов

Итак, мы разобрались со ссылочными типами данных. И узнали, что при переприсваивании объекта копируется ссылка, а не сам объект. Но что делать, если все же нужно создать полную копию? Вот четыре самых популярных и полезных способа создать независимую копию объекта.

Поверхностное копирование через Spread оператор (...)

const product1 = {
 name: "Кофе",
 price: 400,
};

// Создаем реальную копию с помощью Spread оператора
const product2 = { ...product1 };

product2.price = 900;

console.log(product1.price);
console.log(product2.price);
Результат в консоли.
Результат в консоли.

Как это работает? Оператор ... раскрывает все свойства исходного объекта product1 и помещает их в новый объект. По сути, это короткий и элегантный способ скопировать все поля на одном уровне.

Но есть важное ограничение: это поверхностное копирование. Если у вас есть вложенные объекты, они скопируются по ссылке:

const product1 = {
 name: "Кофе",
   details: {
       country: "Бразилия"
   }
};

const product2 = { ...product1 };
product2.details.country = "Колумбия";

console.log(product1.details.country);
Результат в консоли.
Результат в консоли.

Поверхностное копирование через Object.assign()

Это классический метод, который отлично работает и сегодня:

const product1 = {
 name: "Кофе",
 price: 400,
};

// Копируем свойства product1 в новый пустой объект
const product2 = Object.assign({}, product1);

product2.price = 900;

console.log(product1.price);
console.log(product2.price);
Результат в консоли.
Результат в консоли.

Метод Object.assign() берет целевой объект (первый аргумент, у нас это пустой объект {}) и копирует в него свойства из всех последующих объектов (в нашем случае — из product1).

Но, как и Spread оператор, Object.assign() делает только поверхностную копию.

Глубокое копирование через JSON.parse(JSON.stringify())

const product1 = {
 name: "Кофе",
 price: 400,
 details: {
   country: "Бразилия"
 }
};

// Создаем глубокую копию
const product2 = JSON.parse(JSON.stringify(product1));

product2.details.country = "Колумбия";

console.log(product1.details.country);
console.log(product2.details.country);
Результат в консоли.
Результат в консоли.

Этот прием состоит из двух шагов.

  1. JSON.stringify (product1) — превращает объект в строку JSON.

  2. JSON.parse (...) — превращает эту строку обратно в новый, полностью независимый объект.

Но есть важные ограничения. Например, этот метод игнорирует специальные типы данных, такие как undefined, Function и Symbol, а также не копирует методы объекта. Кроме того, он может сломаться, если в объекте есть циклические ссылки (когда объект ссылается сам на себя).

Глубокое копирование через structuredClone()

Помните ограничения JSON-метода? Разработчики JavaScript учли их и добавили в язык специальную функцию для глубокого копирования - structuredClone().

const product1 = {
 name: "Кофе",
 price: 400,
 details: {
   country: "Бразилия"
 },
 tags: ['ароматный', 'горячий']
};

// Создаем настоящую глубокую копию
const product2 = structuredClone(product1);

product2.details.country = "Колумбия";
product2.tags.push('свежий');

console.log(product1.details.country);
console.log(product1.tags);

console.log(product2.details.country);
console.log(product2.tags);
Результат в консоли.
Результат в консоли.

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

Подводим итоги

Мы разобрали, что объекты в JavaScript хранятся и передаются по ссылке. Это значит, что при копировании obj2 = obj1 обе переменные работают с одними данными. Понимание ссылочной природы — это ключ к предотвращению ошибок. Ссылки экономят память, но могут неожиданно изменять данные в разных частях программы.

Если у вас возникли вопросы или сложности при изучении JavaScript, делитесь ими в комментариях — обсудим вместе. В следующих статьях будет еще больше полезных материалов и практических советов — еще увидимся.

Комментарии (7)


  1. ovsale
    23.10.2025 13:41

    вы весь учебник по js будете тут публиковать?


  1. voltag
    23.10.2025 13:41

    console.log(Стоимость ${product1.name}: ${product1.price});

    поставьте кавычки в нужные места, пожалуйста


  1. Zukomux
    23.10.2025 13:41

    structuredClone - "Да, да, пошёл я на хер!"


    1. GCU
      23.10.2025 13:41

      Про него хотя-бы написали, я не понимаю почему нет ни слова про Object.is


  1. Format-X22
    23.10.2025 13:41

    del


  1. Format-X22
    23.10.2025 13:41

    Можно ещё сверх-легкую копию поверх.

    const x = {a: 1, b: 2}; // просто объект
    const y = Object.create(x); // создаем слой сверху
    y.a = 100; // заменяем в слое переменную "a" на свою
    
    console.log(x.a, x.b, y.a, y.b);
    // 1, 2, 100, 2
    // в оригинальном объекте всё также, в нашем новом слое - "a" заменена

    Работает на прототипном движке под капотом JS, так работают классы внутри. В итоге и ссылки сохраняем, и меняем только то что нас интересует.

    Ну и бонусом сверху - "delete y.a;" удалит переменную из слоя, но оставит оригинал. После такого делейта y.a начнет возвращать "1", также как в объекте "x". Ну и конечно любые изменение в "x" отобразятся в "y" если мы не перезаписали в "y" чем-то своим.

    Слоев можно насоздавать любое нужное количество.


  1. AntowaKartowa
    23.10.2025 13:41

    Я вас удивлю, но все типы данных в JavaScript ссылочные. Об этом по сути говорит даже Дэн Абрамов в своём курсе Just JavaScript.