math4tots
math4tots

Reputation: 8870

Are bare objects in javascript part of the ECMAScript standard?

I've come across this article that suggests using 'bare objects' for your hashmap needs if your keys are always strings.

Bare objects are objects created using null as the prototype value, for instance with Object.create(null). Using the object literal notation (i.e. {}) do not create a bare objects since they set Object.prototype as the prototype.

The article points out that the great thing about bare objects is that you can use them as hashmaps without having to worry about builtin keys like toString potentially causing bugs when using a key with the same name.

Is this behavior part of the ES5 and/or ES6 standard? That is, if I use bare objects as string key hashmaps in my code, can I rely on my code behaving in the way that I would expect? Are there any caveats to here?

Upvotes: 4

Views: 949

Answers (2)

Matías Fidemraizer
Matías Fidemraizer

Reputation: 64923

First of all, ECMA-Script 2015 and above has collections like Map. That is, in newer JavaScript implementations you don't need to simulate dictionaries/hashmaps/hashtables with objects anymore.

The article points out that the great thing about bare objects is that you can use them as hashmaps without having to worry about builtin keys like toString potentially causing bugs when using a key with the same name.

The article ignores that you don't need to worry about toString in literal objects because there're well-supported functions to get own object's properties without walking through the prototype chain.

For example, let's say I've declared a literal object as follows: var obj = { text: "Matias" };.

A regular for..in loop would iterate Object.prototype properties, but Object.keys will iterate own object properties only:

Object.keys(obj).forEach(propertyName => {
    var someOwnProperty = obj[propertyName ];
});

In addition, a regular for..in could work as Object.keys using Object.prototype.hasOwnProperty:

for(var propertyName in obj) {
    if(obj.hasOwnProperty(propertyName)) {
       // True if property is declared on obj and not in some 
       // level of the prototype chain
    }
}

AN UPDATE HERE: @bergi is right about something. If obj would declare an own property hasOwnProperty, above for..in wouldn't work because obj.hasOwnProperty wouldn't be Object.prototype.hasOwnProperty anymore.

Imagine that you've the following object that would produce above described issue:

var obj = {
    hasOwnProperty: "hey! I'm not Object.prototype.hasOwnProperty anymore!"
};

That hasOwnProperty would hide Object.prototype.hasOwnProperty.

Above described issue can be circumvented using Object.prototype.hasOwnProperty directly with Function.prototype.call:

for(var propertyName in obj) {
    if(Object.prototype.hasOwnProperty.call(obj, propertyName)) {
       // True if property is declared on obj and not in some 
       // level of the prototype chain
    }
}

Or you could store Object.prototype.hasOwnProperty in a variable to simplify the if statement setting what would be this once the function gets called with Function.prototype.bind:

var hasOwnProperty = Object.prototype.hasOwnProperty.bind(obj);

for(var propertyName in obj) {
    if(hasOwnProperty(propertyName)) {
       // True if property is declared on obj and not in some 
       // level of the prototype chain
    }
}

A side-effect of creating bare objects and using them as prototypes

While you can create bare objects with Object.create(null), the problem comes when a given bare object is the prototype of some other object:

var bareObject = Object.create(null, {
   text: { value: "hello world" }
});

var secondObject = Object.create(bareObject);
secondObject.text2 = "bye!";

for(var property in secondObject) {
   // WAIT, secondObject prototype is a bare object! 
   // And I can't call secondObject.hasOwnProperty to check
   // if the enumerated property is declared in the own object...    
}

If this isn't the case and given object is just a bare object, you can use in operator:

if("someProperty" in bareObject) {

} 

Otherwise, you would need to go with calling Object.prototype.hasOwnProperty with Function.prototype.call or Function.prototype.bind as described above in my answer.

Anyway, as I state at the beginning of my answer, if you're working with ES2015 and above, and you're using a transpiler like BabelJS, you can go with the new JavaScript standard collection types instead of simulating dictionaries with objects.

Is this behavior part of the ES5 and/or ES6 standard? That is, if I use bare objects as string key hashmaps in my code, can I rely on my code behaving in the way that I would expect? Are there any caveats to here?

Object.create was introduced in ECMA-Script 5. It behaves as you expect in almost any modern Web browser and NodeJS.

Upvotes: 5

Bergi
Bergi

Reputation: 664513

Is this behavior part of the ES5 and/or ES6 standard?

Yes. You can find Object.create(null) and the behaviour of looking up / assigning properties closely specified in the ES5, ES6 and ES7 standards.

If I use bare objects as string key hashmaps in my code, can I rely on my code behaving in the way that I would expect?

No. There is no guarantee in the standard that objects are implemented as hashmaps, and there are no complexity/performance assertions either. Implementations might have very slow property lookup on large objects.

Since ES6, you should use Map objects which guarantee sub-linear complexity.

Upvotes: 0

Related Questions