Передача аргументів за значенням і за посиланням
Коли ми передаємо аргументи у функцію, JavaScript копіює їх, а те, як саме копіює, залежить від типу даних: примітиви копіюються за значенням, а об'єкти, масиви і функції передаються за посиланням, точніше за копією посилання. Саме тому одні зміни всередині функції видно ззовні, а інші зникають разом із викликом.
Теорія
TL;DR
- Примітиви (
number,string,boolean,null,undefined,symbol,bigint) передаються за значенням: у параметр лягає повна копія. - Об'єкти, масиви і функції передаються за посиланням, тобто у параметр лягає копія посилання на ту саму ділянку пам'яті.
- Зміна вмісту об'єкта всередині функції (
obj.name = "Bob",arr.push(4)) видно в оригіналі. - Перепризначення параметра (
obj = {...},arr = [...]) оригінал не зачіпає, бо змінюється лише локальна копія посилання. - Строго кажучи, JavaScript завжди передає за значенням: для об'єктів цим значенням є посилання.
Швидкий приклад
let a = 10;
function changeValue(x) {
x = 20; // змінюємо копію
}
changeValue(a);
console.log(a); // 10Значення a не змінилося, бо у x записалася лише його копія.
Передача за значенням
Примітиви, тобто числа, рядки, булеві значення, null, undefined, symbol і bigint, копіюються повністю.
| Тип даних | Як передається |
|---|---|
Примітиви (числа, рядки, булеві, null, undefined, symbol, bigint) | за значенням |
| Об'єкти, масиви, функції | за посиланням (точніше, за копією посилання) |
Пояснення на прикладі вище:
- У
xзаписується копія значенняa. - Зміна
xжодним чином не впливає наa.
У пам'яті це виглядає так:
a -> 10
x -> 10 (копія)Передача за посиланням
Об'єкти, масиви і функції не копіюються цілком. Замість цього передається посилання на їхню адресу в пам'яті.
const user = { name: "Alice" };
function rename(obj) {
obj.name = "Bob";
}
rename(user);
console.log(user.name); // "Bob"Пояснення:
- Змінна
userзберігає посилання на об'єкт у пам'яті. - У
objкопіюється це саме посилання, тож обидві змінні вказують на один і той самий об'єкт. - Зміни через
objвидно і черезuser.
У пам'яті це виглядає так:
user --+
v
{ name: "Bob" }
^
obj ---+Перепризначення посилання всередині функції
Якщо всередині функції змінити саме посилання, оригінал залишиться недоторканим.
const user = { name: "Alice" };
function reassign(obj) {
obj = { name: "Charlie" }; // нове посилання
}
reassign(user);
console.log(user.name); // "Alice"Що тут відбувається:
- У
objспочатку копіюється посилання наuser. - Потім
objперенаправляється на новий об'єкт, але оригінальне посилання (user) залишається тим самим.
Це і є доказ того, що передається копія посилання, а не сам слот змінної.
Приклад з масивом
Масив це теж об'єкт, тому діють ті самі правила. Зміна вмісту видно ззовні:
const numbers = [1, 2, 3];
function modify(arr) {
arr.push(4); // змінюємо вміст
}
modify(numbers);
console.log(numbers); // [1, 2, 3, 4]А ось перепризначення посилання, ні:
function modify(arr) {
arr = [9, 9, 9]; // створюємо новий масив
}
modify(numbers);
console.log(numbers); // [1, 2, 3]Підсумок і проста аналогія
| Тип даних | Як передається | Чи впливає зміна всередині функції на оригінал? |
|---|---|---|
Примітиви (number, string, boolean, null, undefined, symbol, bigint) | За значенням | Ні |
| Об'єкти, масиви, функції | За посиланням (копією посилання) | Так, якщо змінюється вміст |
| Перепризначення параметра всередині функції | Локальна копія | Ні |
Аналогія, яка добре лягає на співбесіді:
- За значенням це як передати копію аркуша паперу: якщо ви щось напишете на своїй копії, оригінал не зміниться.
- За посиланням це як передати адресу сейфа: обидва можете відкрити той самий сейф і змінити те, що всередині.
Типові помилки
- Вважати, що JavaScript має справжню передачу за посиланням. Її немає: функція не може перепризначити змінну, яку їй передали. Правильне формулювання це «передача копії посилання», або, як кажуть англійською, call by sharing.
- Плутати мутацію з перепризначенням.
obj.name = "Bob"змінює спільний об'єкт,obj = {...}змінює лише локальний параметр. - Думати, що
constзахищає від змін.const user = {...}забороняє перепризначитиuser, але не забороняє змінювати його властивості всередині функції. Для захисту потрібенObject.freezeабо копія. - Розраховувати, що поверхнева копія рятує повністю.
{ ...user }іstructuredCloneповодяться по-різному: спред копіює лише верхній рівень, тож вкладені об'єкти залишаються спільними. - Забувати, що рядок незмінний. Метод на кшталт
str.toUpperCase()не змінює аргумент, а повертає новий рядок, який треба присвоїти.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.