Сохраняет ли 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
Мои вопросы (все тесно связаны):
Что именно движок JavaScript Chrome сохраняет для того, чтобы это представление объекта в консоли работало? Это функция конструктора или просто имя функции?
Это удержание необходимо для чего-то более существенного, чем распечатка консоли?
Каково влияние такого удержания на потребление памяти? Что если, например, функция конструктора (или даже ее имя) ненормально огромна?
Это просто 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;
Его внутренняя структура будет выглядеть примерно так (на этот раз пронумеровано, потому что я вернусь к определенным этапам в этой цепочке наследования ниже):
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 {}
Как это для ума? Авторы сценария, пожирайте свои сердца...
В любом случае, я надеюсь, что весь этот дриблинг имел какой-то смысл для кого-то здесь, если нет, я отвечаю на комментарии, так что исправления ошибок, которые я, возможно, сделал, или вопросы относительно некоторой части этого обновления, которые немного неясны, приветствуются...
Я постараюсь ответить на вопрос за вопросом, но, как вы говорите, они все тесно связаны, поэтому ответы частично совпадают.
Читая это, помните, что я написал это за один раз, чувствуя себя немного лихорадочно. Я не эксперт по V8, и основал это на воспоминаниях о том, как я какое-то время копался во внутренностях V8. Ссылка внизу ведет на официальные документы и, конечно, будет содержать более точную и актуальную информацию по этому вопросу.
Что здесь происходит
Что на самом деле делает движок V8 в Chrome, так это создает скрытый класс для каждого объекта, и этот класс отображается в JS-представление объекта.
Или, как люди в Google говорят, что сами
Чтобы сократить время, необходимое для доступа к свойствам JavaScript, V8 не использует динамический поиск для доступа к свойствам. Вместо этого V8 динамически создает скрытые классы за кулисами.
Что происходит в вашем случае, расширение, создание нового конструктора из конкретного экземпляра и переопределение constructor
свойство на самом деле не более, чем то, что вы можете увидеть на этом графике:
Где скрытый класс 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
}
- Правильно:
Function
это нативная конструкция То, как работает V8, означает, что существует класс Function, на который ссылаются все функции. Однако они косвенно ссылаются на этот класс, поскольку каждая функция имеет свои собственные спецификации, которые указываются в производном скрытом классе.create
то следует рассматривать как ссылку наcreate extends HiddenFunction
учебный класс.
Или, если хотите, в синтаксисе C++:class create : public Hidden::Function{/*specifics here*/}
-
Create
функция ссылается на скрытую функцию, идентичнуюcreate
, Однако после его объявления класс получает 1 свойство-свойство, называемоеprototype
, поэтому создается другой скрытый класс, указывающий это свойство. Это основа вашего конструктора. Потому что функция телаcreate
там, где все это происходит, это данность, и V8, вероятно, будет достаточно умен, чтобы заранее создать эти классы, в любом случае: в псевдокоде C++ это будет похоже на листинг 1 кода ниже.
Каждый вызов функции будет назначать ссылку на новый экземпляр скрытого класса, описанного выше,Created
имя, которое является локальным дляcreate
Сфера. Конечно, возвращенный экземплярcreate
все еще сохраняет ссылку на этот экземпляр, но именно так работают области JS, и это относится ко всем двигателям... подумайте о замыканиях, и вы поймете, что я имею в виду (я действительно борюсь с этой противной лихорадкой... извините за это надоедать) - На данном этапе
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 в поле действия вызывающей стороны.