Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Chrome retain each object's constructor?

In Chrome's JavaScript console:

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

> cc = create()
Created {}

> cc
Created {}

Created is a function private to the create function; after create completes, there are no (known to me) references to Created. Yet Chrome can show the function's name at any time, starting from the object created by it.

Chrome didn't achieve this by following the "naïve" approach:

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

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

and anyway, I didn't set constructor on the proto argument passed to create:

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

One guess I had is that the JavaScript VM holds on to Created for the sake of the instanceof mechanism. It is said that instanceof

tests whether an object has in its prototype chain the prototype property of a constructor.

But in the above code I typed create(), effectively passing undefined as prototype; consequently Created doesn't even have its prototype set to the actual cc.__proto__. We can verify this if we hack create to expose the Created function:

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

now let's type

> cc = create()
Created {}

> GlobalCreated
function Created() {}

> GlobalCreated.prototype
undefined

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

My questions (all closely related):

  1. What exactly does Chrome's JavaScript engine retain to make that object presentation in the console work? Is it the constructor function, or just the function name?

  2. Is that retention needed for anything more substantial than console printout?

  3. What is the effect of such retention on memory consumption? What if, for example, the constructor function (or even its name) is abnormally huge?

  4. Is it just Chrome? I've retested with Firebug and Safari, their consoles don't present the object that way. But do they still retain the same data, for other possible purposes (e.g. due to a genuine concern inherent to a JavaScript VM)?

like image 750
Marko Topolnik Avatar asked Jan 27 '14 14:01

Marko Topolnik


People also ask

What is $$ in chrome console?

jQuery style DOM queries in the console The most popular API jQuery provides is the $ , used for selecting DOM elements. The Chrome dev tools console allows you to make use of that $ selector, and more. Under the hood, $ is an alias to document.

How do I view items in chrome console?

Apart from clicking on "More tools-> Developer Tools", we can also open the element box using the following options: Clicking the F12 key. Using keyboard shortcut, "Ctrl + Shift + i" or "Ctrl + Shift + c" on Windows Operating System. The same command works on Chrome OS and Linux.


3 Answers

Late edit:

I recently revisited this question/answer, and I think I've figured out why chrome seems to "hang on" to the Created name. It's not really something that is exclusive to V8, but I think it's the result of how V8 works behind the scenes (the hidden objects I explained in my initial answer), and what V8 is required to do (to conform to the ECMAScript standard).

Any function, constructor functions or otherwise, share the same constructor and prototype-chain by default:

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 {}

This tells us a few things: All functions share the native Function constructor, and inherit from a specific function instance (function Empty(){}) that is used as their prototype. However, a function's prototype property is required to be an object, that the function would return if it were called as a constructor (see ECMAScript standard).

The value of the prototype property is used to initialise the [[Prototype]] internal property of a newly created object before the Function object is invoked as a constructor for that newly created object. This property has the attribute { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }.

We can verify this easily by looking at the Created.prototype.constructor:

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

Now let's, for a moment, list the hidden classes V8 needs to, and probably will, create in order for it to comply to the standard:

function Created(){}

Hidden classes:

  • Object, of course: the mother of all objects, of which Function is a specific child
  • Function: This native object is, as we've demonstrated, the constructor
  • function Empty: The prototype, from which our function will inherit
  • Created our empty function that will inherit from all of the above

At this stage, nothing unusual has happened, and it's self-evident that, when we return an instance of this Created constructor, the Created function will get exposed because of its prototype.
Now, because we're reassigning the prototype property you could argue that this instance will be discarded, and is lost, but from what I understand, that's not how V8 will handle this situation. Instead, it'll create an additional hidden class, that simply overrides the prototype property of its parent after this statement is encountered:

Created.prototype = proto;

Its internal structure will end up looking something like this (numbered this time, because I'll refer back to certain stages within this inheritance chain further down):

  1. Object, of course: the mother of all objects, of which Function is a specific child
  2. Function: This native object is, as we've demonstrated, the constructor
  3. function Empty: The prototype, from which our function will inherit
  4. Created our empty function that will inherit from all of the above
  5. Created2: extends the previous class (Created), and overrides prototype

So why is Created still visible?

That's the million dollar question, to which I think I have the answer now: Optimization

V8 simply can't, nor should it be allowed to, optimize out the Created hidden class (stage 4). Why? Because what will override prototype is an argument. It's something that can't be predicted. What V8 will probably do to optimize the code is to store a hidden object 4, and whenever the create function is called, it'll create a new hidden class that extends stage 4, overriding the prototype property with whatever value is passed to the function.

Because of this, Created.prototype will always exist somewhere inside each instance's internal representation. It's also important to note you could replace the prototype property with one that actually referenced an instance of Created (with a mucked-up prototype chain, but still):

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

How's that for a mind-bender? Inception script-writers, eat your hearts out...

Anyway, I hope all of this dribble made some sense to someone out here, if not, I do respond to comments, so corrections to mistakes I may have made, or questions regarding some part of this update that is a bit unclear are welcome...


I'll try to answer question by question, but as you say, they're all closely related, so the answers overlap up to a point.
While reading this, bare in mind that I wrote this in one go, whilst feeling a bit feverish. I am not a V8 expert, and based this on recollections of my doing some digging in the V8 internals some time ago. The link at the bottom is to the official docs, and will of course contain more accurate and up-to-date information on the subject.

What is going on
What chrome's V8 engine actually does is create a hidden class for each object, and this class is mapped to the JS representation of the object.
Or as the people at google say so themselves:

To reduce the time required to access JavaScript properties, V8 does not use dynamic lookup to access properties. Instead, V8 dynamically creates hidden classes behind the scenes.

What happens in your case, extending, creating a new constructor from a particular instance and overriding the constructor property is actually nothing more than what you can see on this graph:

Google inheritance graph

Where hidden class C0 could be regarded as the standard Object class. Basically, V8 interprets your code, builds a set of C++ like classes, and creates an instance if needed. The JS objects you have are set to point to the different instances whenever you change/add a property.

In your create function, this is -very likely- what is going on:

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. Right: Function is a native construct. The way V8 works means that there is a Function class that is referenced by all functions. They reference this class indirectly, though, because each function has its own specifcs, which are specified in a derived hidden class. create, then, should be seen as a reference to create extends HiddenFunction class.
    Or, if you wish, in C++ syntax: class create : public Hidden::Function{/*specifics here*/}
  2. The Create function references a hidden function identical to create. However, after declaring it, the class receives 1 propriety property, called prototype, so another hidden class is created, specifying this property. This is the basis of your constructor. Because the function body of create, where all of this happens, this is a given, and V8 will probably be clever enough to create these classes beforehand, anyway: in C++ pseudo-code, it'll look similar to code listing 1 below.
    Each function call will assign a reference to a new instance Of the hidden class described above, to the Created name, which is local to create's scope. Of course, the returned instance of create does still retain the reference to this instance, but that's how JS scopes work, and so this applies to all engines... think of closures and you'll get what I mean (I'm really struggling with this nasty fever... sorry to nag about this)
  3. At this stage Create points to an instance of this hidden class, which extends a class that extends a class (as I tried to explain in point 2). Using the new keyword triggers behaviour defined by the Function class, of course (as it's a JS language construct). This results in a hidden class to be created which is probably the same for all instances: it extends the native object, and this has a constructor property, which references the instance of Created we've just made. The instances returned by create though are all alike. Sure their constructors may have a different prototype property, but the objects they churn out all look the same. I'm fairly confident that V8 will only create 1 hidden class for the objects create returns. I can't see why the instances should require different hidden classes: their property names & count are the same, but each instance references another instance, but that's what classes are for

Anyway: code listing for item 2, a pseudo-code representation of what Created might look like in hidden-class terms:

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

Ignore any (likely) syntax oddities (it's been over a year since I wrote a line of C++), and ignoring namespaces and wacky names, The listed classes are, apart from the Hidden::Function effectively all the hidden classes that will ever need to be created to run your code. All your code then does is assign references to instances of these classes. The classes themselves don't take up much space in memory. And any other engine will create just as many objects, because they, too, need to comply with the ECMAScript specs.
So I guess, looking at it like this, this sort of answers all your questions: no not all engines work like this, but this approach won't cause massive amounts of memory to be used, Yes, this does mean a lot of information/data/references to all objects is retained, but that's just an unavoidable, and in some cases happy side-effect of this approach.
Update: I did a bit more digging, and found an example of how you could add JS functions to V8 using templates, it illustrates how V8 translates JS objects/functions to C++ classes, see the example here

This is just me speculating, but I wouldn't at all be surprized to learn that the way V8 works, and this retention business is heavily used in garbage-collection and memory management in general (EG: deleting a property changing hidden classes and the like)
For example:

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

That last bit is just me guessing, but it could be possible for the GC to do this.

What is this retention used for
As I said, in V8, a JS object is actually a sort-of pointer to a C++ class. Accessing properties (and this includes the magic properties of arrays, too!), is fast. Really, really fast. In theory, accessing a property is an O(1) operation.
That's why, on IE:

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

Is faster than:

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

While on chrome, arr.length is faster as shown her. I also answered that question, and it, too, contains some details on V8 you may want to check. It could be that my answer there doesn't (completely) apply anymore, because browsers and their engines change fast...

What about the memory
Not a big problem. Yes, Chrome can be a bit of resource hog at times, but the JS isn't always to blame. Write clean code, and the memory footprint won't be too different on most browsers.
If you create a huge constructor, then V8 will create a larger hidden class, but if that class specifies a lot of properties already, then chances of their being a need for additional hidden classes is smaller.
And of course, each function is an instance of the Function class. This being a native (and very important) type in a functional language will most likely be a highly optimized class anyway.
Anyway: as far as memory usage is concerned: V8 does a pretty good job managing memory. Far better than IE's of old, for example. So much so that the V8 engine is used for server-side JS (as in node.js), if memory really was an issue, then you wouldn't dream of running V8 on a server that must be up and running as much as possible, now would you?

Is this just Chrome
Yes, in a way. V8 does have a special take on how it consumes and runs JS. Rather than JIT-compiling your code to bytecode and running that, it compiles the AST straight into machine code. Again, like the hidden-classes trickery, this is to increase performance.
I know I included this graph in my answer on CR, but just for completeness' sake: Here's a graph that shows the differences between chrome (bottom) and other JS engines (top) Bytecode vs machine-code

Notice that below the bytecode instructions and the CPU, there's an (orange) interpreter layer. That's what V8 doesn't need, owing to the JS being translated into machine code directly.
The downside being that this makes certain optimizations harder to do, especially concerning the ones where DOM data and user input is being used in the code (for example: someObject[document.getElementById('inputField').value]) and that the initial processing of the code is harder on the CPU.
The upside is: once the code is compiled into machine code, it's the fastest you're going to get, and running the code is likely to cause less overhead. A bytecode interpreter is heavier on the CPU most of the time, that's why busy loops on FF and IE can cause the browser to alert the user of a "running script" asking them if they want to stop it.

more on V8 internals here

like image 127
Elias Van Ootegem Avatar answered Oct 21 '22 04:10

Elias Van Ootegem


I don't know much about Chrome's internals, so this is just a guess, but it seems to me that Chrome is performing some kind of static analysis on the code which created the function, and storing that for debugging purposes.

Take a look at this example:

> 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? There's no way JavaScript has any built-in way for you to access the name of the variable a function was initially assigned to. Chrome must be doing that for its own reasons.

Now look at this example:

> 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 {}

In this example, since we created the function in a separate closure before assigning it to a variable, Chrome doesn't know the "name" of the function, so it just says "Object".


These examples lead me to guess the following answers to your questions:

What exactly does Chrome's JavaScript engine retain to make that object presentation in the console work? Is it the constructor function, or just the function name?

It performs a static analysis of the code, and stores a string containing the function's "name" somewhere.

Is that retention needed for anything more substantial than console printout?

Probably not.

What is the effect of such retention on memory consumption? What if, for example, the constructor function (or even its name) is abnormally huge?

I'm not sure, but I'm guessing it's very unlikely to be an issue. Since the name of the function is determined using static analysis, the potential size of the function name is limited by the size of variable names in the script which created it (unless perhaps you're using eval, in which case I'm not sure).

Is it just Chrome? I've retested with Firebug and Safari, their consoles don't present the object that way. But do they still retain the same data, for other possible purposes (e.g. due to a genuine concern inherent to a JavaScript VM)?

I doubt it, this seems to be something specific to Chrome used to make debugging a bit easier. As far as I can tell, there's no other reason for a feature like this to exist.

like image 39
Ajedi32 Avatar answered Oct 21 '22 05:10

Ajedi32


Disclaimer: I am not a Google Chrome expert, however I think that these are not browser-specific, and can be explained by basic Javascript rules.

What exactly does Chrome's JavaScript engine retain to make that object presentation in the console work? Is it the constructor function, or just the function name?

Each Object or Function in Javascript has its inheritance chain, going up, all the way to the basic prototype.

You can not circumvent this by setting the prototype property to undefined, although it may seem like it from the console output.

So it is the whole constructor function that is retained because of inheritance, although not available to be accessed through global scope.

Is that retention needed for anything more substantial than console printout?

Yes, it is needed for the prototype inheritance system to work.

What is the effect of such retention on memory consumption? What if, for example, the constructor function (or even its name) is abnormally huge?

Yes, this can cause a memory leak if used improperly.

This is why you should always delete and clean unused variables, so these and their prototypes can get collected by the garbage collector.

Is it just Chrome? I've retested with Firebug and Safari, their consoles don't present the object that way. But do they still retain the same data, for other possible purposes (e.g. due to a genuine concern inherent to a JavaScript VM)?

This should work the same way across all browsers, because prototypal inheritance works the same. I have however not specifically tested for it. Please note that the console outputs int browsers can differ, and this does not mean anything, as each browser can implement its console in its own way.

like image 41
maksion Avatar answered Oct 21 '22 04:10

maksion