Famabara

Передача по ссылке и передача по значению в JavaScript

dima
3 mo. ago
Насколько хорошо вы знаете JavaScript?
Как в JS передаются значения - по ссылке или по значению? Не спешите, подумайте даже если вы "мидл" или синьор-помидор.
let str1 = 'Famabara';
let str2 = str1; // Тут копия или новая строка?

let obj1 = { name: 'Famabara' };
let obj2 = obj1; // Тут копия или новый объект?

Можете в опросе ответить:
Как передаются значения при присвоении в переменную или в свойство объекта?
Теперь посмотрим, что нам говорит learn.javascript.ru - очень популярный в рунете учебник JavaScript:
А теперь давайте запустим в NodeJS такой код:
const arr = [];

for (let i = 0; i < 1200200; i++) {
  arr.push('0123456789'.repeat(1000 * 1000));
}

while (true) {
}

После запуска смотриим программой top потребление ресурсов:
39858 dima      20   0 2026180   1,0g  37824 R 100,0   3,2   0:21.44 node 
На моей Кубунте сожрано 1 Гб оперативки. Ух ты.

Теперь изменим немного код:
const arr = [];
const text = '0123456789'.repeat(1000 * 1000);

for (let i = 0; i < 1200200; i++) {
  arr.push(text);
}

while (true) {
}

Смотрим ещё раз через top:
40267 dima      20   0 1066148  73796  37440 R  99,7   0,2   0:12.27 node

Программа потребила всего 73 Мб вместо 1 Гб. Согласно популярной теории о копировании примитивов такого быть не должно. ;)

В чем разница между двумя примерами кода? В первом случае мы всякий раз создаём новую строку, а а во втором - пихаем в массив ту же самую строку из созданной переменной. Если вы вдруг подумали, что во втором случае надо предварительно поместить значение в переменную-посредник, а потом уже пушить в массив, то нет - это не поможет, оперативка жраться всё равно не будет.

Кстати, на ноде в Windows 10 аналогичное поведение - около 1 Гб потребление против менее 100.

Ну, а теперь главный секрет! :)
В JavaScript всё передаётся/копируется по ссылке!
Вообще, все эти "передача по ссылке" и "передача по значению" - не совсем корректные термины.
Увы и ах, но в JavaScript есть только один источник правды - это спецификация, а она в JS просто отвратительная по множеству причин.

Но давайте попробуем к ней обратиться.

В ECMAScript 2023 пункте 5.2 написано:
Algorithm steps may declare named aliases for any value using the form “Let x be someValue”. These aliases are reference-like in that both x and someValue refer to the same underlying data and modifications to either are visible to both. Algorithm steps that want to avoid this reference-like behaviour should explicitly make a copy of the right-hand side: “Let x be a copy of someValue” creates a shallow copy of someValue.

Т.е. сказано, что по умолчанию передача по ссылке (reference-like behaviour) и только при прямом указании в спецификации делается копия, причём неглубокая (shallow copy of someValue).

Попытаюсь объяснить поведение хост-среды JS:
// Выражание справа от оператора присваивания создало
// ссылку на строку и вернуло её.
// Это ссылка поместилась в переменную str1.
let str1 = 'Famabara'; 

// Выражание справа от оператора присваивания вернуло ту же ссылку на строку.
// Это ссылка поместилась в переменную str2.
let str2 = str1; 

// Выражание справа от оператора присваивания
// вернуло новую ссылку на строку.
// Это новая ссылка поместилась в переменную str1.
// А переменная str2 содержит "старую" ссылку на строку.
str1 = 'Famabara super';

С подобным поведением движку JS даже проще проводить сравнение. Если с двух сторон у нас одна ссылка, то строки не нужно побайтово сравнивать, можно сразу вернуть true.
+4
27
2
0
2 mo. ago
Вот это супер.
Можно ещё на bun погонять
0
2 mo. ago
Да, можно