In a project I'm maintaining we make extensive use of null prototype objects as a poor man's alternative to (string key only) Maps, which are not natively supported in many older, pre-ES6 browsers.
Basically, to create a null prototype object on the fly, one would use:
var foo = Object.create(null);
This guarantees that the new object has no inherited properties, such as "toString", "constructor", "__proto__" which are not desirable for this particular use case.
Since this pattern appears multiple times in code, we came up with the idea of writing a constructor that would create objects whose prototype has a null prototype and no own properties.
var Empty = function () { };
Empty.prototype = Object.create(null);
Then to create an object with no own or inherited properties one can use:
var bar = new Empty;
In a strive to improve performance, I wrote a test, and found that the native Object.create
approach unexpectedly performs much slower than the method involving an extra constructor with an ad hoc prototype, in all browsers: http://jsperf.com/blank-object-creation.
I was ingenuously expecting the latter method to be slower as it involves invoking a user defined constructor, which doesn't happen in the former case.
What could be the cause of such a performance difference?
Java startup time is often much slower than many languages, including C, C++, Perl or Python, because many classes (and first of all classes from the platform Class libraries) must be loaded before being used.
The Object.assign() method copies all enumerable own properties from one or more source objects to a target object. It returns the modified target object.
You've been investigating something which is highly dependent on the specific version of the browser you are running. Here are some results I get here when I run your jsperf test:
In Chrome 47 new Empty
runs at 63m ops/sec whereas Object.create(null)
runs at 10m ops/sec.
In Firefox 39 new Empty
runs at 733m ops/sec whereas Object.create(null)
runs at 1,685m ops/sec.
("m" above means we're talking about millions.)
So which one do you pick? The method which is fastest in one browser is slowest in the other.
Not only this, but the results we are looking at here are very likely to change with new browser releases. Case in point, I've checked the implementation of Object.create
in v8. Up to December 30th 2015, the implementation of Object.create
was written in JavaScript, but a commit recently changed it to a C++ implementation. Once this makes its way into Chrome, the results of comparing Object.create(null)
and new Empty
are going to change.
But this is not all...
You've looked at only one aspect of using Object.create(null)
to create an object that is going to be used as a kind of map (a pseudo-map). What about access times into this pseudo-map? Here is a test that checks the performance of misses and one that checks the performance of hits.
On Chrome 47 both the hits and miss cases are 90% faster with an object created with Object.create(null)
.
On Firefox 39, the hit cases all perform the same. As for the miss cases, the performance of an object created with Object.create(null)
is so great that jsperf tells me the number of ops/sec is "Infinity".
The results obtained with Firefox 39 are those I was actually expecting. The JavaScript engine should seek the field in the object itself. If it is a hit, then the search is over, no matter how the object was created. If there is a miss on finding the field in the object itself, then the JavaScript engine must check in the object's prototype. In the case of objects created with Object.create(null)
, there is no prototype so the search ends there. In the case of objects created with new Empty
, there is a prototype, in which the JavaScript engine must search.
Now, in the life-time of a pseudo-map how often is the pseudo-map created? How often is it being accessed? Unless you are in a really peculiar situation the map should be created once, but accessed many times. So the relative performance of hits and misses is going to be more important to the overall performance of your application, then the relative performance of the various means of creating the object.
We could also look at the performance of adding and deleting keys from these pseudo-maps, and we'd learn more. Then again, maybe you have maps from which you never remove keys (I've got a few of those) so deletion performance may not be important for your case.
Ultimately, what you should be profiling to improve the performance of your application is your application as a system. In this way, the relative importance of the various operations in your actual application is going to be reflected in your results.
The performance difference has to do with the fact that constructor functions are highly optimized in most JS engines. There's really no practical reason that Object.create couldn't be as fast as constructor functions, it's just an implementation-dependent thing that will likely improve as time goes on.
That being said, all the performance test proves is that you shouldn't be choosing one or the other based on performance because the cost of creating an object is ridiculously low. How many of these maps are you creating? Even the slowest implementation of Object.create on the tests is still chugging out over 8,000,000 objects per second, so unless you have a compelling reasons to create millions of maps, I'd just choose the most obvious solution.
Furthermore, consider the fact that one browser implementation can literally be 100s of times faster than another implementation. This difference is going to exists regardless of which you pick, so the small difference between Object.create and constructors shouldn't really be considered a relevant difference within broader context of different implementations.
Ultimately, Object.create(null) is the obvious solution. If the performance of creating objects becomes a bottleneck, then maybe consider using constructors, but even then I would look elsewhere before I resorted to using something like Empty
constructors.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With