Как стать автором
Обновить

Комментарии 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" - это и то же?).

Нет не тоже, в таком случае в памяти heap создастся три значения

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

Строки как объекты

Почти всё в JavaScript является объектами. Когда вы создаёте строку, например:

let string = 'This is my string';

ваша переменная становится строковым объектом, и, как результат, ей доступно множество свойств и методов.

Так по мне семантически это верно. Думаю так легче новичкам объяснять чем заморачивать им такими деталями. Хотя очень полезно такое знать.

Новичкам возможно и не стоит заморачиваться помимо не использования new 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 посмотреть на что и как ссылаются ваши идентификаторы. Это перевернет Ваш мир)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации