Подтвердить что ты не робот

Как переменная выделяется память в Javascript?

Я хотел бы знать, как локальные переменные выделяют память в javascript. В C и С++ локальные переменные хранятся в стеке. Это то же самое в javascript? или все хранится в куче?

4b9b3361

Ответ 1

На самом деле это очень интересная область JavaScript, и есть как минимум два ответа:

  • Ответ с точки зрения того, что определяет спецификация, и
  • Ответ с точки зрения того, что на самом деле делают движки JavaScript, что может быть оптимизировано (и часто так и есть)

С точки зрения спецификации: способ обработки локальных переменных в JavaScript сильно отличается от способа, которым это делает C. Когда вы вызываете функцию, среди прочего создается лексическая среда для этого вызова, которая имеет нечто, называемое запись среды. Чтобы было проще, я буду называть их обоих вместе "связующим объектом" (хотя есть веские причины, по которым они различаются в спецификации; хотя, если вы хотите углубиться в это, выделите несколько часов и прочитайте спецификацию). Объект привязки содержит привязки для аргументов функции, все локальные переменные, объявленные в функции, и все функции, объявленные в функции (наряду с несколькими другими вещами). Привязка представляет собой комбинацию имени (например, a) и текущего значения для привязки (вместе с парой флагов, о которых нам здесь не нужно беспокоиться). Неквалифицированная ссылка в функции (например, foo в foo, но не foo в obj.foo, которая квалифицирована) сначала проверяется на объекте привязки, чтобы увидеть, соответствует ли он привязке на нем; если это так, эта привязка используется. Когда замыкание сохраняется после возврата функции (что может произойти по нескольким причинам), объект привязки для этого вызова функции сохраняется в памяти, поскольку замыкание имеет ссылку на объект привязки в том месте, где он был создан. Так что в терминах спецификации это все об объектах.

На первый взгляд, это предполагает, что стек не используется для локальных переменных; на самом деле современные движки JavaScript довольно умны и могут (если это того стоит) использовать стек для локальных объектов, которые фактически не используются замыканием. Они могут даже использовать стек для локальных объектов, которые используются замыканием, но затем перемещают их в объект привязки, когда функция возвращается, так что замыкание продолжает иметь доступ к ним. (Естественно, стек все еще используется для отслеживания адресов возврата и тому подобного.)

Вот пример:

function foo(a, b) {
    var c;

    c = a + b;

    function bar(d) {
        alert("d * c = " + (d * c));
    }

    return bar;
}

var b = foo(1, 2);
b(3); // alerts "d * c = 9"

Когда мы вызываем foo, объект привязки создается с этими привязками (согласно спецификации):

  • a и b &— Аргументы функции
  • c &— локальная переменная, объявленная в функции
  • bar &— функция, объявленная внутри функции
  • (... и пару других вещей)

Когда foo выполняет инструкцию c = a + b;, он ссылается на привязки c, a и b на объекте привязки для этого вызова к foo. Когда foo возвращает ссылку на функцию bar, объявленную внутри нее, bar переживает вызов возврата foo. Так как bar имеет (скрытую) ссылку на объект привязки для этого конкретного вызова foo, объект привязки сохраняется (тогда как в обычном случае не было бы выдающихся ссылок на него, и поэтому он был бы доступен для мусора). сбор).

Позже, когда мы вызываем bar, новый объект привязки для этого вызова создается с (среди прочего) привязкой, называемой d &— аргумент к bar. Этот новый объект привязки получает родительский объект привязки: объект, прикрепленный к bar. Вместе они образуют "цепочку сфер". Неквалифицированные ссылки в bar сначала проверяются по объекту привязки для этого вызова в bar, поэтому, например, d разрешает привязку d к объекту привязки для вызова в bar. Но неквалифицированная ссылка, которая не соответствует привязке к этому объекту привязки, затем проверяется по его родительскому объекту привязки в цепочке областей действия, который является объектом привязки для вызова foo, который создал bar. Поскольку это имеет привязку для c, эта привязка используется для идентификатора c в пределах bar. Например, в грубых выражениях:

+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
|   global binding object   |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| ....                      |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
             ^
             | chain
             |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| 'foo' call binding object |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| a = 1                     |
| b = 2                     |
| c = 3                     |
| bar = (function)          |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
             ^
             | chain
             |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| 'bar' call binding object |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| d = 3                     |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+

Интересный факт: эта цепочка областей действия показывает, как глобальные переменные работают в JavaScript. Обратите внимание на "глобальный объект привязки" в приведенном выше. Так что в функции, если вы используете идентификатор, которого нет в объекте привязки для этого вызова функции, и нет ни в одном из других объектов привязки между ним и объектом глобальной привязки, если объект глобальной привязки имеет привязку для этого используется глобальная привязка. Вуаля, глобальные переменные. (ES2015 сделал это немного более интересным, имея два уровня для объекта глобальной привязки: слой, используемый старомодными глобальными объявлениями, такими как var и объявлениями функций, и слой, используемый более новыми, такими как let, const и class. Разница в том, что более старый уровень также создает свойства для глобального объекта, к которому вы как бы обращаетесь через window в браузерах, но более новый уровень этого не делает. Поэтому глобальное объявление let не создает свойство window, но глобальное объявление var делает.)

Реализации свободны использовать любой механизм, который они хотят под прикрытием, чтобы казалось, что вышесказанное происходит. Невозможно получить прямой доступ к объекту привязки для вызова функции, и спецификация ясно дает понять, что совершенно нормально, если объект привязки является просто концепцией, а не буквальной частью реализации. Простая реализация может буквально делать то, что говорит спецификация; более сложный может использовать стек, когда нет задействованных замыканий (для повышения скорости), или может всегда использовать стек, но затем "отрывать" объект привязки, необходимый для замыкания, при извлечении стека. Единственный способ узнать в каждом конкретном случае, это посмотреть на их код. :-)

Подробнее о замыканиях, цепочке областей действия и т.д. Здесь:

Ответ 2

К сожалению, ответ таков: это зависит.

В последние javascript-двигатели произошел большой сдвиг, который стал оптимизировать гораздо лучше, чем раньше. Ранее использовался ответ: "Локальные переменные хранятся в ячейках, выделенных кучей, для закрытия работы". Это не так просто.

Было проведено (или использовалось как 20-30 лет назад) исследование реализаций Схемы и оптимизации закрытия (JavaScript унаследовал довольно много замыканий схемы, за исключением продолжений, которые делают его еще более сложным).

У меня нет готовых ссылок на бумагу, но если у вас нет невероятно эффективного сборщика мусора, вам также нужно использовать стек. Тогда сложная часть имеет дело с закрытием, которое должно иметь распределенные кучи переменных. Для этого используются разные стратегии. В результате получается гибрид, где:

  • с помощью встроенных функций вы можете уменьшить количество выделенных/выделенных кучей кадров, значительно выделяемых/освобожденных
  • некоторые переменные могут быть надежно помещены в стек, поскольку этот временной интервал ограничен (он часто связан с вложением вызовов функций также)
  • В некоторых случаях вы знаете, что вы можете создавать закрытие, но вы можете подождать, пока это произойдет, а затем выделите для него стек стека кучи и скопируйте текущие значения из стека
  • есть оптимизация, связанная с хвостовыми вызовами, где вы можете назначить кучу раньше, а затем повторно использовать фрейм стека для следующего вызова функции, но это не используется в javascript-механизмах, насколько я знаю в настоящее время

это поле очень быстро меняется в нескольких конкурирующих двигателях, поэтому ответ, вероятно, будет "зависит от него"

Кроме того, в новых версиях языка мы увидим такие функции, как let и const, которые на самом деле упрощают для двигателей оптимизацию решений о распределении. Особенно неизменяемость очень помогает, так как вы можете свободно копировать значения из стека (а затем сделать частью объекта замыкания) без разрешения коллизий переменных переменных из разных закрытий.