Сохраняет ли 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
}

теперь пусть type

> cc = create()
Created {}

> GlobalCreated
function Created() {}

> GlobalCreated.prototype
undefined

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

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

  • Что именно поддерживает Chrome JavaScript, чтобы сделать презентацию этого объекта в консоли? Является ли это конструкторной функцией или просто именем функции?

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

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

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

Ответы

Ответ 1

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

Недавно я пересмотрел этот вопрос/ответ, и я думаю, что я понял, почему хром, похоже, "держится" за имя 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;

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

  • Object, конечно: мать всех объектов, из которых Function - конкретный ребенок
  • Function: Этот собственный объект, как мы показали, является конструктором
  • function Empty: прототип, из которого наша функция наследует
  • Created наша пустая функция, которая наследует все вышеперечисленные
  • 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 {}

Как это для умы? Начальные script -авторы, питаются вашими сердцами...

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


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

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

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

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

Google inheritance graph

Если скрытый класс C0 можно рассматривать как стандартный класс Object. В принципе, V8 интерпретирует ваш код, строит набор классов, подобных С++, и при необходимости создает экземпляр. У вас есть объекты 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
}
  • Правильно: Function - это собственная конструкция. Способ работы V8 означает, что существует класс Function, на который ссылаются все функции. Они косвенно ссылаются на этот класс, поскольку каждая функция имеет свои собственные спецификации, которые указаны в производном скрытом классе. create, то следует рассматривать как ссылку на класс create extends HiddenFunction.
    Или, если хотите, в синтаксисе С++: class create : public Hidden::Function{/*specifics here*/}
  • Функция create ссылается на скрытую функцию, идентичную create. Однако после объявления этого класса класс получает 1 свойство правдоподобия, называемое prototype, поэтому создается другой скрытый класс с указанием этого свойства. Это основа вашего конструктора. Поскольку тело функции create, где все это происходит, это заданное значение, и V8, вероятно, будет достаточно умным, чтобы заранее создать эти классы, так или иначе: в псевдокоде С++ он будет похож на код 1 ниже.
    Каждый вызов функции назначает ссылку на новый экземпляр скрытого класса, описанный выше, на имя Created, которое является локальным для области create. Конечно, возвращаемый экземпляр create все еще сохраняет ссылку на этот экземпляр, но то, как работают области JS, и поэтому это относится ко всем моделям... подумайте о закрытии, и вы получите то, что я имею в виду (I ' m действительно борется с этой мерзкой лихорадкой... извините, что нагнал это)
  • На этом этапе 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);
    }
}

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

Это только я размышляю, но я бы совсем не удивился, узнав, что способ 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 фактически является своего рода указателем на класс С++. Доступ к свойствам (и это включает в себя магические свойства массивов тоже!), Быстро. Действительно, очень быстро. Теоретически доступ к свойству является операцией 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 отлично справляется с управлением памятью. Например, гораздо лучше, чем IE. Настолько, что двигатель V8 используется для серверной JS (как в node.js), если память действительно была проблемой, тогда вы не мечтали запускать V8 на сервере, который должен быть запущен и запущен как насколько это возможно, теперь вы?

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

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

подробнее о внутренних функциях V8 здесь

Ответ 2

Я мало знаю о внутренностях 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 был встроенный способ доступа к имени переменной, изначально назначенной функции. Chrome должен делать это по собственному усмотрению.

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

> 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 не знает "имя" функции, поэтому он просто говорит "Объект".


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

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

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

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

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 4

//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);
    }
}

Ответ 5

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

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

Если вы должны удалить "новое" ключевое слово, вы можете открыть функцию "Создано" для области вызова.