Сохраняет ли Chrome конструктор каждого объекта?

В консоли Chrome JavaScript:

> function create(proto) {
    function Created() {}
    Created.prototype = proto
    return new Created
  }
undefined

> cc = create()
Created {}

> cc
Created {}

Created это функция, частная для create функция; после create завершено, нет (известных мне) ссылок на Created, Тем не менее, Chrome может показывать имя функции в любое время, начиная с созданного им объекта.

Chrome не достиг этого, следуя "наивному" подходу:

> cc.constructor
function Object() { [native code] }

> cc.toString()
"object [Object]"

и вообще я не ставил constructor на proto аргумент передан create:

> cc.__proto__.hasOwnProperty("constructor")
false

Одно предположение у меня было, что виртуальная машина JavaScript держится Created ради instanceof механизм. Он сказал, что instanceof

проверяет, имеет ли объект в своей цепочке прототипов свойство prototype конструктора.

Но в приведенном выше коде я набрал create() эффективно проходя undefined в качестве прототипа; как следствие Created даже не имеет своего prototype установить на фактический cc.__proto__, Мы можем проверить это, если мы взломать create разоблачить Created функция:

function create(proto) {
  function Created() {}
  Created.prototype = proto
  GlobalCreated = Created
  return new Created
}

теперь давайте наберем

> cc = create()
Created {}

> GlobalCreated
function Created() {}

> GlobalCreated.prototype
undefined

> cc instanceof GlobalCreated
TypeError: Function has non-object prototype 'undefined' in instanceof check

Мои вопросы (все тесно связаны):

  1. Что именно движок JavaScript Chrome сохраняет для того, чтобы это представление объекта в консоли работало? Это функция конструктора или просто имя функции?

  2. Это удержание необходимо для чего-то более существенного, чем распечатка консоли?

  3. Каково влияние такого удержания на потребление памяти? Что если, например, функция конструктора (или даже ее имя) ненормально огромна?

  4. Это просто Chrome? Я протестировал Firebug и Safari, их консоли не представляют объект таким образом. Но сохраняют ли они те же данные для других возможных целей (например, из-за подлинной заботы, присущей JavaScript-виртуальной машине)?

5 ответов

Позднее редактирование:

Я недавно вернулся к этому вопросу / ответу, и мне кажется, что я понял, почему хром, кажется, "держится" на Created название. Это на самом деле не то, что является эксклюзивным для V8, но я думаю, что это результат того, как V8 работает за кулисами (скрытые объекты, которые я объяснил в моем первоначальном ответе), и что V8 требуется сделать (чтобы соответствовать стандарту ECMAScript),

Любая функция, функции конструктора или другие, по умолчанию совместно используют один и тот же конструктор и цепочку прототипов:

function Created(){};
console.log(Created.constructor);//function Function() { [native code] }
console.log(Object.getPrototypeOf(Created));//function Empty() {}
console.log(Created.__proto__);//same as above
console.log(Created.prototype);//Created {}

Это говорит нам о нескольких вещах: все функции имеют общий Function конструктор и наследовать от конкретного экземпляра функции (function Empty(){}), который используется в качестве их прототипа. Тем не менее, функция prototype Свойство обязательно должно быть объектом, который функция возвращала бы, если бы она была вызвана как конструктор (см. стандарт ECMAScript).

Значение свойства prototype используется для инициализации внутреннего свойства [[Prototype]] вновь созданного объекта, прежде чем объект Function вызывается в качестве конструктора для этого вновь созданного объекта. Это свойство имеет атрибут { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }.

Мы можем легко это проверить, посмотрев на Created.prototype.constructor:

console.log(Created.prototype.constructor);//function Created() {}

Теперь давайте на мгновение перечислим скрытые классы, которые V8 должен и, вероятно, создаст, чтобы он соответствовал стандарту:

function Created(){}

Скрытые классы:

  • Object Конечно, мать всех объектов, из которых Function это конкретный ребенок
  • Function: Этот нативный объект, как мы показали, конструктор
  • function Empty: Прототип, от которого будет наследоваться наша функция
  • Created наша пустая функция, которая будет наследовать от всего вышеперечисленного

На этом этапе ничего необычного не произошло, и само собой разумеется, что, когда мы возвращаем экземпляр этого Created конструктор Created функция будет выставлена ​​из-за своего прототипа.
Теперь, потому что мы переназначаем prototype Можно утверждать, что этот экземпляр будет отброшен и потерян, но, насколько я понимаю, V8 ​​не справится с этой ситуацией. Вместо этого он создаст дополнительный скрытый класс, который просто переопределяет prototype свойство его родителя после того, как встречается это утверждение:

Created.prototype = proto;

Его внутренняя структура будет выглядеть примерно так (на этот раз пронумеровано, потому что я вернусь к определенным этапам в этой цепочке наследования ниже):

  1. Object Конечно, мать всех объектов, из которых Function это конкретный ребенок
  2. Function: Этот нативный объект, как мы показали, конструктор
  3. function Empty: Прототип, от которого будет наследоваться наша функция
  4. Created наша пустая функция, которая будет наследовать от всего вышеперечисленного
  5. Created2: расширяет предыдущий класс (Created) и переопределяет prototype

Так почему же Created все еще видно?

Это вопрос на миллион долларов, на который я думаю, что у меня есть ответ сейчас: Оптимизация

V8 просто не может и не должен оптимизировать Created скрытый класс (этап 4). Зачем? Потому что то, что переопределит prototype это аргумент. Это то, что нельзя предсказать. То, что V8, вероятно, сделает для оптимизации кода, это сохранить скрытый объект 4, и всякий раз, когда create вызывается функция, она создаст новый скрытый класс, который расширяет этап 4, переопределяя prototype свойство с любым значением передается функции.

Из-за этого, Created.prototype всегда будет существовать где-то внутри внутреннего представления каждого экземпляра. Также важно отметить, что вы можете заменить prototype свойство с тем, что на самом деле ссылается на экземпляр Created (с испорченной цепочкой прототипов, но все же):

cc = create();
console.log(Object.getPrototypeOf(cc))//Object {}
cc = create(new GlobalCreated);
console.log(Object.getPrototypeOf(cc));//Created {}

Как это для ума? Авторы сценария, пожирайте свои сердца...

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


Я постараюсь ответить на вопрос за вопросом, но, как вы говорите, они все тесно связаны, поэтому ответы частично совпадают.
Читая это, помните, что я написал это за один раз, чувствуя себя немного лихорадочно. Я не эксперт по V8, и основал это на воспоминаниях о том, как я какое-то время копался во внутренностях V8. Ссылка внизу ведет на официальные документы и, конечно, будет содержать более точную и актуальную информацию по этому вопросу.

Что здесь происходит
Что на самом деле делает движок V8 в Chrome, так это создает скрытый класс для каждого объекта, и этот класс отображается в JS-представление объекта.
Или, как люди в Google говорят, что сами

Чтобы сократить время, необходимое для доступа к свойствам JavaScript, V8 не использует динамический поиск для доступа к свойствам. Вместо этого V8 динамически создает скрытые классы за кулисами.

Что происходит в вашем случае, расширение, создание нового конструктора из конкретного экземпляра и переопределение constructor свойство на самом деле не более, чем то, что вы можете увидеть на этом графике:

График наследования Google

Где скрытый класс C0 можно рассматривать как стандарт Object учебный класс. По сути, V8 интерпретирует ваш код, создает набор классов, подобных C++, и при необходимости создает экземпляр. У вас есть объекты JS, указывающие на различные экземпляры при каждом изменении / добавлении свойства.

В вашем create функция, это очень вероятно, что происходит:

function create(proto)
{//^ creates a new instance of the Function class -> cf 1 in list below
    function Created(){};//<- new instance of Created hidden class, which extends Function cf 2
    function Created.prototype = proto;//<- assigns property to Created instance
    return new Created;//<- create new instance, cf 3 for details
}
  1. Правильно: Function это нативная конструкция То, как работает V8, означает, что существует класс Function, на который ссылаются все функции. Однако они косвенно ссылаются на этот класс, поскольку каждая функция имеет свои собственные спецификации, которые указываются в производном скрытом классе. create то следует рассматривать как ссылку на create extends HiddenFunction учебный класс.
    Или, если хотите, в синтаксисе C++: class create : public Hidden::Function{/*specifics here*/}
  2. Create функция ссылается на скрытую функцию, идентичную create, Однако после его объявления класс получает 1 свойство-свойство, называемое prototype, поэтому создается другой скрытый класс, указывающий это свойство. Это основа вашего конструктора. Потому что функция тела create там, где все это происходит, это данность, и V8, вероятно, будет достаточно умен, чтобы заранее создать эти классы, в любом случае: в псевдокоде C++ это будет похоже на листинг 1 кода ниже.
    Каждый вызов функции будет назначать ссылку на новый экземпляр скрытого класса, описанного выше, Created имя, которое является локальным для create Сфера. Конечно, возвращенный экземпляр create все еще сохраняет ссылку на этот экземпляр, но именно так работают области JS, и это относится ко всем двигателям... подумайте о замыканиях, и вы поймете, что я имею в виду (я действительно борюсь с этой противной лихорадкой... извините за это надоедать)
  3. На данном этапе Create указывает на экземпляр этого скрытого класса, который расширяет класс, который расширяет класс (как я пытался объяснить в пункте 2). С использованием new поведение ключевых слов, определяемое Function класс, конечно (так как это языковая конструкция JS). Это приводит к созданию скрытого класса, который, вероятно, одинаков для всех экземпляров: он расширяет собственный объект и имеет constructor свойство, которое ссылается на экземпляр Created мы только что сделали. Экземпляры возвращены create хотя все похожи. Конечно, их конструкторы могут иметь разные prototype собственность, но объекты, которые они производят, все выглядят одинаково. Я довольно уверен, что V8 создаст только 1 скрытый класс для объектов create возвращается. Я не понимаю, почему экземплярам требуются разные скрытые классы: имена и количество их свойств одинаковы, но каждый экземпляр ссылается на другой экземпляр, но для этого нужны классы.

В любом случае: листинг кода для пункта 2, псевдокодовое представление того, что Created может выглядеть в терминах скрытого класса:

//What a basic Function implementation might look like
namespace Hidden
{//"native" JS types
    class Function : public Object
    {
        //implement new keyword for constructors, differs from Object
        public:
            Function(...);//constructor, function body etc...
            Object * operator new ( const Function &);//JS references are more like pointers
            int length;//functions have a magic length property
            std::string name;
    }
}
namespace Script
{//here we create classes for current script
    class H_create : public Hidden::Function
    {};
    class H_Created : public Hidden::Function
    {};//just a function
    class H_Created_with_prototype : public H_Created
    {//after declaring/creating a Created function, we add a property
     //so V8 will create a hidden class. Optimizations may result in this class
     // being the only one created, leaving out the H_Created class
        public: 
            Hidden::Object prototype;
    }
    class H_create_returnVal : public Hidden::Object
    {
        public:
            //the constructor receives the instance used as constructor
            //which may be different for each instance of this value
            H_create_returnVal(H_Created_with_prototype &use_proto);
    }
}

Проигнорируйте любые (вероятные) странности синтаксиса (это было больше года, так как я написал строку C++), и игнорируя пространства имен и дурацкие имена. Перечисленные классы, кроме Hidden::Function фактически все скрытые классы, которые когда-либо понадобятся для запуска вашего кода. Все, что делает ваш код, это назначает ссылки на экземпляры этих классов. Сами классы не занимают много места в памяти. И любой другой движок создаст столько же объектов, потому что они тоже должны соответствовать спецификациям ECMAScript.
Так что я думаю, что если посмотреть на это так, то это ответ на все ваши вопросы: нет, не все движки работают так, но этот подход не приведет к использованию огромных объемов памяти. Да, это означает, что много информации / data / ссылки на все объекты сохраняются, но это просто неизбежно, и в некоторых случаях это побочный эффект такого подхода.
Обновление: я немного покопался и нашел пример того, как вы можете добавлять функции JS в V8 с помощью шаблонов, он иллюстрирует, как V8 переводит объекты / функции JS в классы C++, см. Пример здесь

Это только мое предположение, но я не удивлюсь, узнав, как работает V8, и этот бизнес по хранению активно используется для сбора мусора и управления памятью в целом (например, удаление свойства, изменение скрытых классов и лайк)
Например:

var foo = {};//foo points to hidden class Object instance (call id C0)
foo.bar = 123;//foo points to child of Object, which has a property bar (C1)
foo.zar = 'new';//foo points to child of C1, with property zar (C2)
delete foo.zar;//C2 level is no longer required, foo points to C1 again

Этот последний бит только я догадываюсь, но GC может сделать это.

Для чего используется эта задержка
Как я уже говорил, в V8 объект JS на самом деле является своего рода указателем на класс C++. Доступ к свойствам (включая магические свойства массивов!) Происходит быстро. Действительно, очень быстро. Теоретически доступ к свойству является операцией O(1).
Вот почему в IE:

var i,j;
for(i=0,j=arr.length;i<j;++i) arr[i] += j;

Быстрее чем:

for (i=0;i<arr.length;++i) arr[i] += arr.length;

Пока на хроме, arr.length быстрее, как показано ей. Я также ответил на этот вопрос, и он также содержит некоторые подробности о V8, которые вы можете проверить. Возможно, мой ответ там (полностью) больше не применим, потому что браузеры и их движки быстро меняются...

Как насчет памяти
Не большая проблема. Да, время от времени Chrome может быть немного трудоемким, но JS не всегда виноват. Напишите чистый код, и объем памяти не будет слишком отличаться в большинстве браузеров.
Если вы создаете огромный конструктор, то V8 создаст скрытый класс большего размера, но если этот класс уже определяет много свойств, то вероятность того, что им понадобятся дополнительные скрытые классы, будет меньше.
И, конечно же, каждая функция является экземпляром Function учебный класс. Это родной (и очень важный) тип в функциональном языке, скорее всего, в любом случае будет высокооптимизированным классом.
В любом случае: что касается использования памяти: V8 довольно хорошо справляется с управлением памятью. Например, намного лучше, чем в старых. Настолько, что ядро ​​V8 используется для JS на стороне сервера (как в node.js), если память действительно была проблемой, то вы не мечтали бы запустить V8 на сервере, который должен быть запущен столько, сколько возможно, сейчас бы вы?

Это просто Chrome
Да, в некотором смысле. V8 имеет особый взгляд на то, как он потребляет и запускает JS. Вместо того, чтобы JIT-компилировать ваш код в байт-код и запускать его, он компилирует AST прямо в машинный код. Опять же, как обман скрытых классов, это должно повысить производительность.
Я знаю, что включил этот график в свой ответ на CR, но просто для полноты: вот график, который показывает различия между Chrome (внизу) и другими движками JS (вверху) Байт-код против машинного кода

Обратите внимание, что под инструкциями байт-кода и процессором находится (оранжевый) слой интерпретатора. Это то, что V8 не нужно, поскольку JS напрямую переводится в машинный код.
Недостатком является то, что это затрудняет выполнение определенных оптимизаций, особенно в отношении тех, где данные DOM и пользовательский ввод используются в коде (например: someObject[document.getElementById('inputField').value]) и что начальная обработка кода труднее на процессоре.
Плюс: после того, как код скомпилирован в машинный код, он будет самым быстрым, и выполнение кода, вероятно, приведет к меньшим накладным расходам. Интерпретатор байт-кода в большинстве случаев тяжелее на процессоре, поэтому циклы занятости в FF и IE могут заставить браузер предупреждать пользователя о "запущенном скрипте", спрашивая его, хотят ли они его остановить.

больше о внутренностях V8 здесь

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

Взгляните на этот пример:

> function create(proto) {
    object = {}
    object.x = {}
    x = object.x
    x.func = function() {}
    x.func.prototype = proto
    return new object.x.func
}
undefined
> create()
x.func {}

x.func? В JavaScript нет встроенного способа доступа к имени переменной, которой изначально была назначена функция. Хром должен делать это по своим собственным причинам.

Теперь посмотрим на этот пример:

> function newFunc() {
  return function() {}
}

> function create(proto) {
    object = {}
    object.x = {}
    x = object.x
    x.func = newFunc()
    x.func.prototype = proto
    return new object.x.func
}
undefined
> create()
Object {}

В этом примере, поскольку мы создали функцию в отдельном замыкании перед присвоением ее переменной, Chrome не знает "имени" функции, поэтому он просто говорит "Object".


Эти примеры заставляют меня угадать следующие ответы на ваши вопросы:

Что именно движок JavaScript Chrome сохраняет для того, чтобы это представление объекта в консоли работало? Это функция конструктора или просто имя функции?

Он выполняет статический анализ кода и сохраняет где-то строку, содержащую "имя" функции.

Это удержание необходимо для чего-то более существенного, чем распечатка консоли?

Возможно нет.

Каково влияние такого удержания на потребление памяти? Что если, например, функция конструктора (или даже ее имя) ненормально огромна?

Я не уверен, но я предполагаю, что это вряд ли будет проблемой. Поскольку имя функции определяется с помощью статического анализа, потенциальный размер имени функции ограничен размером имен переменных в сценарии, который ее создал (если, возможно, вы не используете evalв таком случае я не уверен).

Это просто Chrome? Я протестировал Firebug и Safari, их консоли не представляют объект таким образом. Но сохраняют ли они те же данные для других возможных целей (например, из-за подлинной заботы, присущей JavaScript-виртуальной машине)?

Я сомневаюсь, что это, кажется, что-то особенное в Chrome, чтобы облегчить отладку. Насколько я могу судить, нет другой причины существования такой функции.

Отказ от ответственности: я не эксперт по Google Chrome, однако считаю, что они не относятся к конкретному браузеру и могут быть объяснены основными правилами Javascript.

Что именно движок JavaScript Chrome сохраняет для того, чтобы это представление объекта в консоли работало? Это функция конструктора или просто имя функции?

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

Вы не можете обойти это, установив свойство prototype в значение undefined, хотя может показаться, что оно выводится из консоли.

Таким образом, вся функция конструктора сохраняется из-за наследования, хотя недоступна для доступа через глобальную область видимости.

Это удержание необходимо для чего-то более существенного, чем распечатка консоли?

Да, это необходимо для работы системы наследования прототипа.

Каково влияние такого удержания на потребление памяти? Что если, например, функция конструктора (или даже ее имя) ненормально огромна?

Да, это может привести к утечке памяти при неправильном использовании.

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

Это просто Chrome? Я протестировал Firebug и Safari, их консоли не представляют объект таким образом. Но сохраняют ли они те же данные для других возможных целей (например, из-за подлинной заботы, присущей JavaScript-виртуальной машине)?

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

//The real method to do clone
function doClone(source, keys, values, result) {
    if (source == null || typeof (source) !== "object") {
        return source;
    }
    if (source.Clone instanceof Function) {
        return source.Clone();
    }
    if (source instanceof Date) {
        if (!(result instanceof Date)) {
            result = new Date();
        }
        result.setTime(source.getTime());
        return result;
    }
    else if (source instanceof Array) {
        if (!(result instanceof Array)) {
            result = [];
        }
        for (var i = 0; i < source.length; i++) {
            result[i] = clone(source[i], keys, values, result[i]);
        }
        return result;
    }
    try {
        if (typeof result !== "object" || result == null) {
            result = new source.constructor();
        } else {
            result.constructor = source.constructor;
        }
        if (source.prototype) {
            result.prototype = source.prototype;
        }
        if (source.__proto__) {
            result.__proto__ = source.__proto__;
        }
    } catch (e) {
        if (Object.create) {
            result = Object.create(source.constructor.prototype);
        } else {
            result = {};
        }
    }
    if (result != null) {
        // ReSharper disable once MissingHasOwnPropertyInForeach
        for (var property in source) {
            if (source.hasOwnProperty(property)) {
                try {
                    var descriptor = Object.getOwnPropertyDescriptor(source, property);
                    if (descriptor != null) {
                        if (descriptor.get || descriptor.set) {
                            Object.defineProperty(result, property, descriptor);
                        } else {
                            descriptor.value = clone(descriptor.value, keys, values, result[property]);
                            Object.defineProperty(result, property, descriptor);
                        }
                    } else {
                        result[property] = clone(source[property], keys, values, result[property]);
                    }
                } catch (e) {
                    result[property] = clone(source[property], keys, values, result[property]);
                }
            }
        }
    }
    return result;
}

//The portal of clone method
function clone(source, keys, values, result) {
    var index = keys.indexOf(source);
    if (index !== -1) {
        return values[index];
    }
    result = doClone(source, keys, values, result);
    index = keys.indexOf(source);
    if (index !== -1) {
        values[index] = result;
    } else {
        keys.push(source);
        values.push(result);
    }
    return result;
}

/**
 * Core functions
 */
var X = {
    /**
     * Clone indicated source instance
     * @param {} source The source instance to be clone
     * @param {} target If indicated, copy source instance to target instance.
     * @returns {} 
     */
    Clone: function (source, target) {
        return clone(source, [], [], target);
    }
}

Вы возвращаете новый экземпляр из create в объект с именем Created.

create()()
> TypeError: object is not a function

Если бы вы удалили ключевое слово "new", вы бы открыли функцию Created в поле действия вызывающей стороны.

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