Avatar ·

JavaScript: передача по значению или по ссылке? Разбираемся с путаницей

Всем привет! Я новичок в JavaScript и никак не могу понять, как же на самом деле работает передача аргументов в функции. В одних статьях пишут, что JS передаёт всё по значению, в других — что объекты передаются по ссылке. Я написал простой код, который меня окончательно запутал. Вот пример: ```javascript function changeValue(x) { x = 10; console.log('Внутри функции:', x); } let a = 5; changeValue(a); console.log('После функции:', a); // 5 — всё понятно, число не изменилось ``` С этим всё ясно — примитив передаётся по значению. Но когда я пробую с объектом: ```javascript function changeObject(obj) { obj.name = 'Новое имя'; } let user = { name: 'Старое имя' }; changeObject(user); console.log(user.name); // 'Новое имя' — объект изменился! Получается, по ссылке? ``` Но ещё больше меня сбивает с толку этот пример: ```javascript function replaceObject(obj) { obj = { name: 'Совсем другой объект' }; console.log('Внутри:', obj.name); } let user2 = { name: 'Исходный' }; replaceObject(user2); console.log(user2.name); // 'Исходный' — почему? Ведь объект же по ссылке! ``` Так как же это работает на самом деле? Можете объяснить простыми словами, что происходит под капотом? Почему в одном случае объект меняется, а в другом — нет? И что значит эта фраза "передача по значению ссылки", которую я иногда встречаю? <<>>

Короткий ответ: JavaScript всегда передаёт аргументы по значению

Но тут есть важный нюанс. Когда вы передаёте объект, вы передаёте значение ссылки на этот объект. Многие путают это с передачей по ссылке, но разница принципиальная. Давайте разберёмся на примерах.

Что такое "значение ссылки"?

Представьте, что объект в памяти — это дом, а переменная — это листок бумаги с адресом этого дома. Когда вы передаёте объект в функцию, вы не передаёте сам дом и не даёте ключи от него. Вы просто даёте копию листка с адресом. У вас остаётся оригинальный листок, а у функции — его копия.

Теперь разберём ваши примеры:

Пример 1: Изменение свойств объекта

function changeObject(obj) {
    obj.name = 'Новое имя'; // Идём по адресу и меняем дом
}

let user = { name: 'Старое имя' };
changeObject(user);
console.log(user.name); // 'Новое имя'

Здесь всё логично. У вас и у функции есть копии одного и того же адреса. Функция идёт по адресу (своей копии) и перекрашивает дом. Вы смотрите на тот же дом по своему адресу и видите изменения. Дом один, адресов два, но ведут они к одному месту.

Пример 2: Попытка заменить объект целиком

function replaceObject(obj) {
    obj = { name: 'Совсем другой объект' }; // Стираем свой адрес и пишем новый
}

let user2 = { name: 'Исходный' };
replaceObject(user2);
console.log(user2.name); // 'Исходный'

А вот тут ключевой момент. Когда вы пишете obj = { ... }, вы не идёте по адресу и не меняете дом. Вы просто берёте свою копию листка с адресом, стираете его и записываете новый адрес (нового дома). Оригинальный листок (переменная user2) остаётся без изменений. У вас по-прежнему старый адрес, и вы смотрите на старый дом.

Наглядная аналогия

  • Примитивы (числа, строки, булевы): Вы передаёте копию самого дома (маленькую копию). Что бы функция ни делала со своей копией, оригинал не пострадает.
  • Объекты (включая массивы и функции): Вы передаёте копию адреса дома. Функция может войти в дом и переставить мебель (изменить свойства), но не может заставить вас смотреть на другой дом (перезаписать вашу переменную).

Почему это не "передача по ссылке"?

В языках с настоящей передачей по ссылке (например, C# с ключевым словом ref или C++ с &) функция может изменить саму переменную вызывающего кода. В JavaScript это невозможно. Если бы JS передавал объекты по ссылке, то второй пример заменил бы user2 на новый объект. Этого не происходит.

Проверьте сами

function testRef(obj) {
    obj = null; // Попытка "удалить" объект
}

let myObj = { data: 123 };
testRef(myObj);
console.log(myObj); // { data: 123 } — объект жив!

Если бы это была передача по ссылке, myObj стал бы null. Но он остаётся прежним. Это окончательно доказывает, что JavaScript передаёт значение ссылки, а не саму ссылку.

Итог

  • JavaScript всегда передаёт аргументы по значению.
  • Для объектов этим значением является ссылка (адрес в памяти).
  • Вы можете изменить содержимое объекта через эту ссылку, но не можете изменить то, на что указывает оригинальная переменная.
<<>>

Дополнение: как это работает с массивами

Та же логика применима и к массивам, которые в JS являются объектами:

function modifyArray(arr) {
    arr.push(4); // Изменяем содержимое
}

function replaceArray(arr) {
    arr = [1, 2, 3]; // Пытаемся заменить
}

let nums = [1, 2];
modifyArray(nums);
console.log(nums); // [1, 2, 4] — массив изменился

replaceArray(nums);
console.log(nums); // [1, 2, 4] — без изменений

Почему возникает путаница?

Многие разработчики приходят из языков вроде Java, где концепция точно такая же. Там тоже говорят "объекты передаются по ссылке", но на самом деле это "передача значения ссылки". Разница становится критичной, когда вы пишете код, который должен модифицировать исходную переменную.

Практический совет

Если вам нужно, чтобы функция заменила объект в вызывающем коде, возвращайте новое значение:

function createNewObject() {
    return { name: 'Новый объект' };
}

let myObj = { name: 'Старый' };
myObj = createNewObject(); // Явное присваивание
console.log(myObj.name); // 'Новый объект'

Или используйте мутацию свойств, если это уместно:

function updateObject(obj) {
    Object.assign(obj, { name: 'Обновлённый' });
}

let myObj = { name: 'Старый' };
updateObject(myObj);
console.log(myObj.name); // 'Обновлённый'

Понимание этой концепции поможет вам избежать многих багов, особенно при работе с функциями высшего порядка, колбэками и иммутабельными структурами данных.