Комментарии 21
А с каких пор строки стали примитивами?
Такова терминология 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
В JS как всегда всё странно. Строки в JS бывают примитивами или объектами. Интерфейс у них при этом одинаковый:
на строковых примитивах возможно использовать методы объекта String. В контекстах, когда на примитивной строке вызывается метод или происходит поиск свойства, JavaScript автоматически оборачивает строковый примитив объектом и вызывает на нём метод или ищет в нём свойство.
Но проблемы всё же бывают:
код может сломаться, если он получает объекты String, а ожидает строковые примитивы, хотя в общем случае вам не нужно беспокоиться о различиях между ними.
Авторский код "word".repeat(1000000);
очевидно возвращает объект, который далее передаётся по ссылке. Чтобы получить примитив строки из объекта строки, нужно использовать метод valueOf()
- "word".repeat(1000000).valueOf()
.
@Vindrix попробуйте начать читать мануалы.
Метод repeat
возвращает примитивную строку, поэтому valueOf()
было бы актуально к
let objStr = new String("word");
let primStr = objStr.valueOf();
Нет, это совсем другое. Вы говорите про "объектные обертки" к примитивным типам. А данная статья именно о примитивных типах, которые под капотом нифига не примитивные.
"word".repeat(1000000); возвращает именно строку, а не объектную обертку, и valueOf тут ничего не поменяет.
Ну вообще не стоит создавать инстансы строк через new, а то можно на тех же проверках типов полететь.
Это что? Незнание того что существуют неизменяемые типы? Строка это неизменяемый ссылочный тип.
Выглядит как заметка об иммутабельности строк. В c#, например, интернирование строк тема достаточно интересная, но большая часть темы уже касается не простого запихивания одних и тех же констант в массив, а о поведении при операциях со строками, вроде склеивания (var word ="word" и var word2 = "wo" + "rd" - это и то же?).
Нашел на мдне ссылочку полезную, где прямо написано, что строка - строковый объект, так что неудивительно, что будут похожие оптимизации.
Почти всё в JavaScript является объектами. Когда вы создаёте строку, например:
let string = 'This is my string';
ваша переменная становится строковым объектом, и, как результат, ей доступно множество свойств и методов.
Так по мне семантически это верно. Думаю так легче новичкам объяснять чем заморачивать им такими деталями. Хотя очень полезно такое знать.
То что строки на самом деле объекты, в общем-то интуитивно понятно. Меня больше удивило, что даже числа могут быть объектами. "Истинных" примитивов в JS немного, вроде только целые 32-разрядные числа со знаком
В JS вроде примитивные значения "заварачиваются" в специальный объект (может кто-то объяснит что на самом деле происходит), когда происходит доступ к методу или любму свойству.
Я пробовал этоNumber.prototype.myFunc = function () {return this}
var t = 2
t.myFunc() === t.myFunc() // false
То есть кажется, что каждый раз создается новая обертка.
Да и числа могут храниться по-разному, например в массиве движки могут оптимизировать, например так.
Вывод некорректный по итогу. А эксперимент годный. Я как раз недавно пытался по новой погрузиться в эту тему(удалось не до конца, пока на паузе).
Стоит заметить, что сразу можно откинуть источники инфы, которые пользуются определением "ссылочный/примитивный". Видимо это тянется с 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
Внимание, ниже мой вывод, который может быть вообще некорректный и требует валидации.
Как я понял каждое значение имеет некие характеристики, которым можно описывать само значение(в том числе ссылка на значение в памяти). И если с так называемыми примитивами эти характеристики легко получить, то с объектами - нет.
Если я ошибся, то поправьте, но только с ссылками на офф доку, потому что тема оч интересная
Согласно спецификации JS не существует таких понятий как примитивный или ссылочный тип данных (привет @demimurych).
"JavaScript движки оптимизируют использование памяти"
Это верно. Но здесь, с большой вероятностью, речь идет о применении оптимизационного механизма на стадии компиляции кода внутри движка (к сожалению, наименования оптимизационного правила на вскидку не припомню).
Js исполняется на виртуальной машине, если я не ошибаюсь. Конкретно хром юзает V8. Как это работает:
1) Ignition парсит и превращает в байт код для виртуальной машины. Пример какой то функции ниже:
На 59 строчке можно увидеть Constant pool. Это то откуда берутся какие то значения. Если у тебя функция () { return 0.1 }. То этот 0.1 будет у тебя хранится тоже в Constant pool.
Возьмем 63 строчку. Левее можно увидеть 0x0ce7772c16c9. Это адрес в памяти, где хранится эта сущность.
Короче, у каждой функции в V8 создаётся свой Constant Pool. В байт-коде функция получает адреса этих констант в памяти. Но если значения полностью совпадают, V8 не дублирует их, а просто даёт ссылку на уже существующую сущность. Например, если в одной функции две переменные содержат одинаковую строку, в Constant Pool хранится только одна копия этой строки. На втором рисунке видно, что в байт-коде обе переменные загружаются из одной константы, потому что V8 типо оптимизировал хранение
[generated bytecode for function: (0x26ec08fbaf71 <SharedFunctionInfo>)]
Bytecode length: 120
Parameter count 5
Register count 8
Frame size 64
Bytecode age: 0
23 S> 0x26ec08fbb0e6 @ 0 : 2d 06 00 00 GetNamedProperty a3, [0], [0]
0x26ec08fbb0ea @ 4 : c4 Star0
128 S> 0x26ec08fbb0eb @ 5 : 13 01 LdaConstant [1]
0x26ec08fbb0ed @ 7 : be Star6
128 E> 0x26ec08fbb0ee @ 8 : 62 04 f4 02 CallUndefinedReceiver1 a1, r6, [2]
0x26ec08fbb0f2 @ 12 : bf Star5
71 E> 0x26ec08fbb0f3 @ 13 : 2d f5 02 04 GetNamedProperty r5, [2], [4]
0x26ec08fbb0f7 @ 17 : c3 Star1
101 E> 0x26ec08fbb0f8 @ 18 : 2d f5 03 06 GetNamedProperty r5, [3], [6]
0x26ec08fbb0fc @ 22 : c2 Star2
198 S> 0x26ec08fbb0fd @ 23 : 13 04 LdaConstant [4]
0x26ec08fbb0ff @ 25 : be Star6
198 E> 0x26ec08fbb100 @ 26 : 62 04 f4 08 CallUndefinedReceiver1 a1, r6, [8]
0x26ec08fbb104 @ 30 : bf Star5
179 E> 0x26ec08fbb105 @ 31 : 2d f5 05 0a GetNamedProperty r5, [5], [10]
0x26ec08fbb109 @ 35 : c1 Star3
246 S> 0x26ec08fbb10a @ 36 : 11 LdaTrue
0x26ec08fbb10b @ 37 : be Star6
246 E> 0x26ec08fbb10c @ 38 : 62 f9 f4 0c CallUndefinedReceiver1 r1, r6, [12]
0x26ec08fbb110 @ 42 : c0 Star4
281 S> 0x26ec08fbb111 @ 43 : 61 f8 0e CallUndefinedReceiver0 r2, [14]
367 S> 0x26ec08fbb114 @ 46 : 78 06 10 00 00 CreateRegExpLiteral [6], [16], #0
0x26ec08fbb119 @ 51 : be Star6
0x26ec08fbb11a @ 52 : 13 07 LdaConstant [7]
0x26ec08fbb11c @ 54 : bd Star7
367 E> 0x26ec08fbb11d @ 55 : 63 fa f4 f3 11 CallUndefinedReceiver2 r0, r6, r7, [17]
398 S> 0x26ec08fbb122 @ 60 : 13 08 LdaConstant [8]
0x26ec08fbb124 @ 62 : be Star6
402 E> 0x26ec08fbb125 @ 63 : 62 f7 f4 13 CallUndefinedReceiver1 r3, r6, [19]
0x26ec08fbb129 @ 67 : bf Star5
0x26ec08fbb12a @ 68 : 13 09 LdaConstant [9]
448 E> 0x26ec08fbb12c @ 70 : 6c f5 15 TestEqualStrict r5, [21]
0x26ec08fbb12f @ 73 : 99 16 JumpIfFalse [22] (0x26ec08fbb145 @ 95)
466 S> 0x26ec08fbb131 @ 75 : 13 0a LdaConstant [10]
0x26ec08fbb133 @ 77 : bd Star7
466 E> 0x26ec08fbb134 @ 78 : 62 04 f3 16 CallUndefinedReceiver1 a1, r7, [22]
0x26ec08fbb138 @ 82 : be Star6
502 E> 0x26ec08fbb139 @ 83 : 2d f4 0b 18 GetNamedProperty r6, [11], [24]
0x26ec08fbb13d @ 87 : bf Star5
503 E> 0x26ec08fbb13e @ 88 : 5e f5 f4 f6 1a CallProperty1 r5, r6, r4, [26]
0x26ec08fbb143 @ 93 : 8a 19 Jump [25] (0x26ec08fbb15c @ 118)
1114 S> 0x26ec08fbb145 @ 95 : 13 0c LdaConstant [12]
0x26ec08fbb147 @ 97 : bd Star7
1114 E> 0x26ec08fbb148 @ 98 : 62 04 f3 1c CallUndefinedReceiver1 a1, r7, [28]
0x26ec08fbb14c @ 102 : be Star6
1152 E> 0x26ec08fbb14d @ 103 : 2d f4 0d 1e GetNamedProperty r6, [13], [30]
0x26ec08fbb151 @ 107 : be Star6
1159 E> 0x26ec08fbb152 @ 108 : 2d f4 0e 20 GetNamedProperty r6, [14], [32]
0x26ec08fbb156 @ 112 : bf Star5
1160 E> 0x26ec08fbb157 @ 113 : 5e f5 f4 f6 22 CallProperty1 r5, r6, r4, [34]
0x26ec08fbb15c @ 118 : 0e LdaUndefined
1181 S> 0x26ec08fbb15d @ 119 : a9 Return
Constant pool (size = 15)
0x26ec08fbb029: [FixedArray] in OldSpace
- map: 0x20d17c240211 <Map(FIXED_ARRAY_TYPE)>
- length: 15
0: 0x0ce7772c16c9 <String[19]: #RegExpPrototypeExec>
1: 0x26ec08fbad79 <String[30]: #internal/process/pre_execution>
2: 0x0ce7772d21f9 <String[26]: #prepareMainThreadExecution>
3: 0x0ce7772d2771 <String[21]: #markBootstrapComplete>
4: 0x26ec08fbada9 <String[16]: #internal/options>
5: 0x0ce7772c4df1 <String[14]: #getOptionValue>
6: 0x20d17c243651 <String[1]: #^>
7: 0x20d17c240701 <String[0]: #>
8: 0x26ec08fbade9 <String[27]: #--experimental-default-type>
9: 0x0ce7772c25b1 <String[6]: #module>
10: 0x26ec08fbae19 <String[25]: #internal/modules/run_main>
11: 0x0ce7772d2be9 <String[21]: #executeUserEntryPoint>
12: 0x26ec08fbae49 <String[27]: #internal/modules/cjs/loader>
13: 0x20d17c246ac1 <String[6]: #Module>
14: 0x26ec08fbae79 <String[7]: #runMain>
Handler Table (size = 0)
Source Position Table (size = 64)
0x26ec08fbb161 <ByteArray[64]>
[generated bytecode for function: Habr (0x19a61efd4879 <SharedFunctionInfo Habr>)]
Bytecode length: 9
Parameter count 1
Register count 2
Frame size 16
Bytecode age: 0
44 S> 0x19a61efd59ee @ 0 : 13 00 LdaConstant [0]
0x19a61efd59f0 @ 2 : c4 Star0
70 S> 0x19a61efd59f1 @ 3 : 13 00 LdaConstant [0]
0x19a61efd59f3 @ 5 : c3 Star1
87 S> 0x19a61efd59f4 @ 6 : 0b fa Ldar r0
96 S> 0x19a61efd59f6 @ 8 : a9 Return
Constant pool (size = 1)
0x19a61efd59a1: [FixedArray] in OldSpace
- map: 0x32773ca40211 <Map(FIXED_ARRAY_TYPE)>
- length: 1
0: 0x19a61efd5939 <String[10]: #Hello Habr>
Handler Table (size = 0)
Source Position Table (size = 10)
0x19a61efd59f9 <ByteArray[10]>
Ну тут скорее вопрос терминологии. И о каком уровне абстракции идёт речь. На уровне семантики языка строка - примитивный тип, тут всё нормально. А под капотом да, ситуация посложнее.
Кстати, мне недавно попадалась информация, что вроде бы V8 по каким-то своим причинам эмулирует floating point арифметику! То есть это не нативные операции CPU, а числа на самом деле тоже как бы объекты. Сам я это утверждение пока не проверял, руки не дошли. Если кто-то в курсе как на самом деле - сообщите.
Забавная статья, для джуна сойдет)) вообще ссылаться на learnjs (это было в коментах, а не в статье), это плохой пример убедить читателей в компетентности. В спеке ecma, если открыть про строки, тут же прочитаете, что они имутабельны. Из этого можно сделать вывод, а для чего в памяти хранить копии одного значения, если каждое есть имутабельно? Подумав, можно придти к выводу, что смысла нет) а значит как то они все же передаются по ссылке. Тут стоит еще капнуть глубже и понять, что в js не существует переменных как таковых, вы не найдете такого термина в спецификации. Все, что вы называете переменными в js на самом деле называется идентификаторами которые всегда по ссылке соединены со значением. Исключениями, в рамках реализации различных рантаймов, могут быть числа. В v8 числа до 2^31-1 хранятся в smi а не в heap. Вы можете ознакомится, как например можно запустить node js с флагом --allow-natives-syntax, и с помощью команды %DebugPrint посмотреть на что и как ссылаются ваши идентификаторы. Это перевернет Ваш мир)
https://habr.com/ru/articles/774548/ более подробный разбор
Примитивы в JavaScript — это миф?