Объекты в JavaScript

Примитивные значения хранятся в стеке в javascript, но объекты хранятся в куче. Я понимаю, почему хранить примитивы в стеке, но есть ли причина, по которой объекты хранятся в кучах?

3 ответа

Решение

На самом деле, в JavaScript даже примитивы хранятся в куче, а не в стеке (хотя см. Примечание ниже разрыва ниже). Когда элемент управления входит в функцию, создается контекст выполнения (объект) для этого вызова функции, который имеет переменный объект. Все vars и аргументы функции (плюс пара других вещей) являются свойствами этого объекта анонимной переменной, точно так же, как другие свойства именованных объектов. Стек вызовов используется, но спецификация не требует, чтобы стек использовался для "локального" хранения переменных, и закрытие JavaScript сделало бы использование стека a'la C, C++ и т. Д. Непрактичным. Подробности в спецификации.

Вместо этого используется цепочка (связанный список). Когда вы ссылаетесь на неквалифицированный символ, интерпретатор проверяет переменный объект на предмет текущего контекста выполнения, чтобы узнать, есть ли у него свойство для этого имени. Если так, это привыкает; если нет, проверяется следующий переменный объект в цепочке областей действия (обратите внимание, что он находится в лексическом порядке, а не в порядке вызова, как стек вызовов), и так далее, пока не будет достигнут глобальный контекст выполнения (глобальный контекст выполнения имеет переменный объект, как и любой другой контекст выполнения). Переменный объект для глобальной EC - единственный, к которому мы можем получить прямой доступ в коде: this указывает на это в коде глобальной области видимости (и в любой функции, вызываемой без this быть явно установленным). (В браузерах у нас есть другой способ прямого доступа к нему: объект глобальной переменной имеет свойство, называемое window что он использует, чтобы указать на себя.)

Ваш вопрос о том, почему объекты хранятся в куче: потому что они могут быть созданы и выпущены независимо друг от друга. C, C++ и другие, которые используют стек для локальных переменных, могут делать это, потому что переменные могут (и должны) быть уничтожены при возврате функции. Стек - хороший эффективный способ сделать это. Но объекты не создаются разрушенными таким простым способом; Три объекта, созданные одновременно, могут иметь радикально разные жизненные циклы, поэтому стек для них не имеет смысла. А поскольку локальные объекты JavaScript хранятся в объектах, и у этих объектов есть жизненный цикл, который (потенциально) не связан с функцией, возвращающей... ну, вы поняли идею.:-) В JavaScript стек в значительной степени предназначен только для адресов возврата.


Тем не менее, стоит отметить, что только потому, что все так, как описано выше концептуально, это не означает, что двигатель должен делать это таким образом под капотом. Пока это работает внешне, как описано в спецификации, реализации (движки) могут делать то, что им нравится. Я понимаю, что V8 (движок JavaScript от Google, используемый в 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 если вам не нужно, и вам почти никогда не придется.)

Но это детали реализации. Концептуально, все как описано выше перерыва.

Размер объектов может расти динамически. Поэтому вам нужно будет отрегулировать их требования к памяти. Вот почему они хранятся в куче.

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

Нет ни одного примитивного значения / объекта, который не является свойством другого объекта. (Единственное исключение здесь - глобальный объект).

Другие вопросы по тегам