Все мы знаем что в JavaScript есть ссылочные (Object), присваивающиеся по ссылке и примитивные типы данных (String, Number, Null и тд), присваивающиеся по значению. Но так ли это на самом деле? В этой статье с помощью небольшого эксперимента мы убедимся, что это не совсем так и посмотрим как "примитивные" типы данных на самом деле хранятся в памяти.
Создадим небольшой HTML файл:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<a class="la1"></a>
<body>
<script>
var word = "word".repeat(1000000);
document.querySelector('.a1').innerHTML = word;
</script>
</body>
</html>
Мы создаем переменную word в которой хранится строка, содержащая 1 миллион повторений слова "word"
Сделаем snapshot во вкладке Memory в браузере Chrome и посмотрим на память
Мы видим что наша огромная строка занимает 4Мб памяти
Далее добавим в наш HTML файл немного кода
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<a class="a1"></a>
<a class="a2"></a>
<body>
<script>
var word = "word".repeat(1000000);
// Добавляем переменную word2
var word2 = word;
document.querySelector('.a1').innerHTML = word;
// Отрисовываем ее значение во вторую ссылку
document.querySelector('.a2').innerHTML = word;
</script>
</body>
</html>
Как мы все много раз слышали, примитивные типы передаются по значению, соответственно в переменную word2 должна попасть полная копия значения, которое лежит в переменной word и составляет 4Мб. В итоге мы ожидаем увидеть увеличение памяти в 2 раза.
Давайте смотреть!
Но как так! Память, занимаемая строками не то что не увеличилась в 2 раза, она вообще не изменилась! И мы не наблюдаем второй строки, переменная word2 ссылается на тот же участок памяти, что и переменная word. Никакой копии не произошло. Попробуем по-другому.
Давайте создадим массив и добавим туда несколько значений
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<a class="a1"></a>
<body>
<script>
var word = "word".repeat(1000000)
var arr = [word, word, word, word, word]
for (let i = 0; i < arr.length - 1; i++) {
document.querySelector('.a1').innerHTML += word;
}
</script>
</body>
</html>
Как мы видим, снова ничего не изменилось. Все 5 элементов массива ссылаются на один и тот же участок памяти.
Мы можем взглянуть на сам массив и увидеть его Shallow Size - объем памяти, непосредственно занимаемый объектом, который не включает память, занимаемую другими объектами, на которые этот объект ссылается.
Вывод
JavaScript движки оптимизируют использование памяти, храня данные в одном месте и создавая ссылки на одну и ту же строку для всех переменных, которые на неё ссылаются.
Однако, если одна из переменных, ссылающихся на одну и ту же строку, изменяется, движок создаст новую строку, которая будет занимать соответствующий объем памяти.
Это позволяет эффективно использовать память и избегать её ненужного увеличения.
Комментарии (20)
ARad
26.06.2024 07:26Это что? Незнание того что существуют неизменяемые типы? Строка это неизменяемый ссылочный тип.
Stawros
26.06.2024 07:26Выглядит как заметка об иммутабельности строк. В c#, например, интернирование строк тема достаточно интересная, но большая часть темы уже касается не простого запихивания одних и тех же констант в массив, а о поведении при операциях со строками, вроде склеивания (var word ="word" и var word2 = "wo" + "rd" - это и то же?).
Avangardio
26.06.2024 07:26Нашел на мдне ссылочку полезную, где прямо написано, что строка - строковый объект, так что неудивительно, что будут похожие оптимизации.
Почти всё в JavaScript является объектами. Когда вы создаёте строку, например:
let string = 'This is my string';
ваша переменная становится строковым объектом, и, как результат, ей доступно множество свойств и методов.
senfiaj
26.06.2024 07:26Так по мне семантически это верно. Думаю так легче новичкам объяснять чем заморачивать им такими деталями. Хотя очень полезно такое знать.
Avangardio
26.06.2024 07:26Новичкам возможно и не стоит заморачиваться помимо не использования new String(…), а потом вместе с теми же скрытыми классами узнать, когда будет интересно.
Alexandroppolus
26.06.2024 07:26То что строки на самом деле объекты, в общем-то интуитивно понятно. Меня больше удивило, что даже числа могут быть объектами. "Истинных" примитивов в JS немного, вроде только целые 32-разрядные числа со знаком
senfiaj
26.06.2024 07:26В JS вроде примитивные значения "заварачиваются" в специальный объект (может кто-то объяснит что на самом деле происходит), когда происходит доступ к методу или любму свойству.
Я пробовал этоNumber.prototype.myFunc = function () {return this}
var t = 2
t.myFunc() === t.myFunc() // false
То есть кажется, что каждый раз создается новая обертка.
Да и числа могут храниться по-разному, например в массиве движки могут оптимизировать, например так.
Gary_Ihar
26.06.2024 07:26+1Вывод некорректный по итогу. А эксперимент годный. Я как раз недавно пытался по новой погрузиться в эту тему(удалось не до конца, пока на паузе).
Стоит заметить, что сразу можно откинуть источники инфы, которые пользуются определением "ссылочный/примитивный". Видимо это тянется с ES 5 https://262.ecma-international.org/5.1/#sec-4.3.2
В новой спеке используются понятия " Values without identity/ Values with identity " и то ...
https://tc39.es/ecma262/multipage/notational-conventions.html#sec-identity
Внимание, ниже мой вывод, который может быть вообще некорректный и требует валидации.
Как я понял каждое значение имеет некие характеристики, которым можно описывать само значение(в том числе ссылка на значение в памяти). И если с так называемыми примитивами эти характеристики легко получить, то с объектами - нет.
Если я ошибся, то поправьте, но только с ссылками на офф доку, потому что тема оч интереснаяsergeygolovan
26.06.2024 07:26Согласно спецификации JS не существует таких понятий как примитивный или ссылочный тип данных (привет @demimurych).
"JavaScript движки оптимизируют использование памяти"
Это верно. Но здесь, с большой вероятностью, речь идет о применении оптимизационного механизма на стадии компиляции кода внутри движка (к сожалению, наименования оптимизационного правила на вскидку не припомню).
dom1n1k
26.06.2024 07:26+4Ну тут скорее вопрос терминологии. И о каком уровне абстракции идёт речь. На уровне семантики языка строка - примитивный тип, тут всё нормально. А под капотом да, ситуация посложнее.
Кстати, мне недавно попадалась информация, что вроде бы V8 по каким-то своим причинам эмулирует floating point арифметику! То есть это не нативные операции CPU, а числа на самом деле тоже как бы объекты. Сам я это утверждение пока не проверял, руки не дошли. Если кто-то в курсе как на самом деле - сообщите.
ViktorVovk
26.06.2024 07:26+1Забавная статья, для джуна сойдет)) вообще ссылаться на learnjs (это было в коментах, а не в статье), это плохой пример убедить читателей в компетентности. В спеке ecma, если открыть про строки, тут же прочитаете, что они имутабельны. Из этого можно сделать вывод, а для чего в памяти хранить копии одного значения, если каждое есть имутабельно? Подумав, можно придти к выводу, что смысла нет) а значит как то они все же передаются по ссылке. Тут стоит еще капнуть глубже и понять, что в js не существует переменных как таковых, вы не найдете такого термина в спецификации. Все, что вы называете переменными в js на самом деле называется идентификаторами которые всегда по ссылке соединены со значением. Исключениями, в рамках реализации различных рантаймов, могут быть числа. В v8 числа до 2^31-1 хранятся в smi а не в heap. Вы можете ознакомится, как например можно запустить node js с флагом --allow-natives-syntax, и с помощью команды %DebugPrint посмотреть на что и как ссылаются ваши идентификаторы. Это перевернет Ваш мир)
Skipy
А с каких пор строки стали примитивами?
Lazytech
Такова терминология JavaScript.
https://learn.javascript.ru/primitives-methods
ЦИТАТА:
Есть 7 примитивных типов: string, number, boolean, symbol, null, undefined и bigint.
https://developer.mozilla.org/en-US/docs/Glossary/Primitive
ЦИТАТА:
There are 7 primitive data types:
string
number
bigint
boolean
undefined
symbol
null
AcckiyGerman
В JS как всегда всё странно. Строки в JS бывают примитивами или объектами. Интерфейс у них при этом одинаковый:
Но проблемы всё же бывают:
Авторский код
"word".repeat(1000000);
очевидно возвращает объект, который далее передаётся по ссылке. Чтобы получить примитив строки из объекта строки, нужно использовать методvalueOf()
-"word".repeat(1000000).valueOf()
.@Vindrix попробуйте начать читать мануалы.
Vindrix Автор
Метод
repeat
возвращает примитивную строку, поэтомуvalueOf()
было бы актуально кAlexandroppolus
Нет, это совсем другое. Вы говорите про "объектные обертки" к примитивным типам. А данная статья именно о примитивных типах, которые под капотом нифига не примитивные.
"word".repeat(1000000); возвращает именно строку, а не объектную обертку, и valueOf тут ничего не поменяет.
Avangardio
Ну вообще не стоит создавать инстансы строк через new, а то можно на тех же проверках типов полететь.