Примитивные значения хранятся в стеке в javascript, но объекты хранятся в куче. Я понимаю, почему хранить примитивы в стеке, но по какой причине объекты хранятся в кучах?
Объекты в javascript
Ответ 1
На самом деле, в JavaScript даже примитивы хранятся в куче, а не в стеке (см. примечание ниже разрыва ниже, хотя). Когда элемент управления входит в функцию, создается контекст выполнения (объект) для этого вызова функции, который имеет переменный объект. Все var
и аргументы функции (плюс несколько других вещей) являются свойствами этого анонимного объекта переменной, точно так же как и другие свойства именованных объектов. Используется стек вызовов, но спецификация не требует, чтобы стек использовался для "локального" хранилища переменных, а закрытие JavaScript делало бы использование стека a'la C, С++ и т.д. Для этого непрактичного. Подробности в спецификация.
Вместо этого используется цепочка (связанный список). Когда вы ссылаетесь на неквалифицированный символ, интерпретатор проверяет объект переменной для текущего контекста выполнения, чтобы узнать, имеет ли он свойство для этого имени. Если это так, он используется; если нет, проверяется следующий объект переменной в цепочке областей видимости (обратите внимание, что это находится в лексическом порядке, а не в порядке вызова, таком как стек вызовов) и так далее, пока не будет достигнут глобальный контекст выполнения (глобальный контекст выполнения имеет переменный объект, как и любой другой контекст выполнения). Объект переменной для глобального EC является единственным, с которым мы можем напрямую обращаться в коде: this
указывает на него в глобальном коде области (и в любой функции, которая называется без this
, явно заданной). (В браузерах у нас есть другой способ прямого доступа к нему: объект глобальной переменной имеет свойство window
, которое он использует, чтобы указать на себя.)
Возьмите вопрос о том, почему объекты хранятся в куче: потому что они могут быть созданы и выпущены независимо друг от друга. C, С++ и другие, которые используют стек для локальных переменных, могут сделать это, потому что переменные могут (и должны) быть уничтожены при возврате функции. Стек - хороший эффективный способ сделать это. Но объекты не создаются разрушенными таким простым способом; три объекта, созданные одновременно, могут иметь радикально разные жизненные циклы, поэтому стек не имеет для них смысла. И поскольку локали JavaScript хранятся на объектах, и эти объекты имеют жизненный цикл, который (потенциально) не связан с возвращаемой функцией... ну, вы получаете эту идею.:-) В JavaScript, стек в значительной степени предназначен только для обратных адресов.
Однако, стоит отметить, что только потому, что вещи как описано выше концептуально, это не означает, что движок должен делать это под капотом. До тех пор, пока он работает внешне, как описано в спецификации, реализации (двигатели) могут делать то, что им нравится. Я понимаю, что V8 (движок Google JavaScript, используемый в Chrome и в других местах) делает некоторые очень умные вещи, например, используя стек для локальных переменных (и даже локальные распределения объектов внутри функции), а затем только копирование их в кучу, если необходимо (например, потому что контекст выполнения или отдельные объекты на нем выдержали вызов). Вы можете видеть, как в большинстве случаев это минимизирует фрагментацию кучи и восстанавливает память, используемую для временных ресурсов, более агрессивно и эффективно, чем полагаться на GC, потому что контекст выполнения, связанный с большинством вызовов функций, не нуждается в пережитке вызова. Рассмотрим пример:
function foo() {
var n;
n = someFunctionCall();
return n * 2;
}
function bar() {
var n;
n = someFunction();
setCallback(function() {
if (n === 2) {
doThis();
}
else {
doThat();
}
});
}
В приведенном выше примере движок V8, который агрессивно оптимизирует, может обнаружить, что концептуальный контекст выполнения для вызова foo
никогда не должен выживать, когда возвращается foo
. Таким образом, V8 может свободно выделять этот контекст в стеке и использовать механизм стека для очистки.
Напротив, контекст выполнения, созданный для вызова bar
, должен оставаться рядом после возврата bar
, потому что на нем полагается замыкание (анонимная функция, которую мы передали в setCallback
). Поэтому при компиляции bar
(поскольку V8 компилируется в машинный код на лету), V8 вполне может использовать другую стратегию, фактически выделяя объект контекста в куче.
(Если какой-либо из вышеперечисленных способов использовал eval
каким-либо образом, кстати, скорее всего, V8 и другие двигатели даже не пытаются оптимизировать какую-либо формулу, потому что eval
вводит слишком много режимов отказа оптимизации. еще одна причина не использовать eval
, если вам это не нужно, и вам почти никогда не придется.)
Но это детали реализации. Концептуально все происходит так, как описано выше перерыва.
Ответ 2
Размер объектов может динамически увеличиваться. Поэтому вам нужно будет настроить свои требования к памяти. Вот почему они хранятся в куче.
Ответ 3
Оба примитивных значения и объекты всегда хранятся в каком-то другом объекте - они являются свойствами некоторого объекта.
Существует не одно примитивное значение/объект, который не является свойством другого объекта. (Единственным исключением здесь является глобальный объект).