Extending the JavaScript Object Model

December 17, 2010

in JavaScript,Web Standards

At the core of the JavaScript language is its “object model”. An object model defines the object abstraction of an language.  It tells users how to think about objects in a language — how are objects composed and what they can do.  It also tells language implementers what they must manifest as an object to users of the language.

JavaScript has a very simple object model.  An object is essentially just a set of key/value pairs called properties where the keys are string values.  In addition each object has an “inherits properties from” relationship with another object.  Some users and some implementers actually think in terms of a slightly more complex object model where in addition to string key/value pair properties an object may have indexed properties where the keys are integers.  However, that elaboration isn’t really essential to the understanding of the JavaScript object model because such integer keys can be understood in terms of their string representations.

Developers of JavaScript implementations spend a lot of time designing ways to optimizing their implementation of the JavaScript object model.   The simplicity of the object model allows for a very simple implementation, but such simple implementations will typically have very poor performance.  In order to have excellent performance implementers need to develop mechanisms that optimize the implementation while still maintaining the JavaScript programmer’s perception of it simple basic object model.  These mechanisms typically include complex caching and analysis techniques that try to transparently eliminate most of the dynamic overhead of the object model.

The object model defines many programers’ understanding of JavaScript and it plays a central role  in the design of JavaScript implementations. For these reasons, any major proposal to extend JavaScript needs to be critically examined for its impact upon the existing JavaScript object mode.  If the extension requires a major change to the object model, it may be difficult for programers to understand and use.  For implementers, even seemly simple object model changes may require significant redesign of existing optimization mechanisms and the invention of new techniques.

When possible, it is probably better to try to support a new requirement by extending some existing characteristic of the object model rather than by adding something that is totally new and unrelated to anything in the current object model.  Such extensions are more likely to be understood by programmers and to most easily fit into existing implementation designs.  For example, ECMAScript 5 added the concept of accessor (getter/setter) properties to the object model.  It did this by extending what can constitute the “value” part of a property.  Similarly, the Private Names proposal for ECMAScript Harmony extends what can constitute the “key” part of a property.  Both proposal are similar in that they building upon preexisting object property characteristics.  They don’t add major new concepts to the object model that are not directly related to properties.

There may be future situations that justify the conceptual and implementation cost of extending the JavaScript object model with concepts that are not related to properties.  However, the likely benefit of such an extension needs to be very large. For that reason, I want to propose a principle that any designer of a JavaScript extension should use as a starting point.  Try to work within the basic key/value pair property and prototype inheritance design of the current JavaScript model. Only introduce new non-property concepts into the object model as a last resort.

{ 7 comments }

Peter van der Zee December 19, 2010 at 3:03 am

I’d like to extend that proposal. Not just trying to make future extensions use existing mechanics but also to keep the language as simple, transparent and straightforward as it has been.

I feel, for instance, that getters and setters violated this principle. Properties went from a “read-write” storage mechanism to a magical being, where anything could happen. The win for being able to do stuff with getters and setters doesn’t seem to make up for this loss of transparency.

Proxies don’t seem to suffer from this problem so much, since they can be seen as a completely separated mechanism. I still don’t really agree with them, but in this regard they seem fine.

However, your “private” proposal faces the same problem as above. It would introduce properties that aren’t really there, but they are, but they aren’t, but .. wait wut? That’s exactly why I have a problem with it. It’s degrading the transparency of js even further because it’s introducing a way of properties to be added which aren’t always there.

With the way things are going, at some point js will stop being a simple language. And at that point it will lose it’s attractiveness to new programmers. Is that what we want?

Kevin Smith December 20, 2010 at 9:57 am

I’ve enjoyed reading the proposal, but can not the same be done (less confusingly) with a library function?

function definePrivateProperty(obj, desc)
{
var key = getUniqueKey();

if (typeof desc.enumerable === “undefined”)
desc.enumerable = false;

Object.defineProperty(obj, key, desc);
return key;
}

function Point()
{
var x = definePrivateProperty(this, { value: 100 });
this[x]; // 100
}

allen December 20, 2010 at 10:49 am

Partially…all the underlying support for private name values would still need to be implemented.

The big thing that the private name proposal adds over that is the ability to use dot notation for property access rather than [ ] and to define properties using object literals that have private names.

For example, the following is not currently legal:


var key = getUniqueKey();
return {key: computeSomeValue()}

while in my proposal the equivalent is:

private key;
return {key: computeSomeValue()}

Kevin Smith December 20, 2010 at 10:55 am

Right – I would think that a library could create pretty robust unique names using a URI prefix, perhaps.

I grant that the [] notation is cumbersome, though, and using the name as a key in object literals is enticing (didn’t think of that one!).

alex j December 20, 2010 at 1:45 pm

Only introduce new non-property concepts into the object model as a last resort.

Including modules? Could the private keyword be part of module underpinnings?

alex j December 20, 2010 at 2:03 pm

Also, could private be used for traits/mixins?

FremyCompany December 21, 2010 at 4:21 am

I’ve had some reflexion on the current ‘private names’ proposal, and I must say I’m still unhappy with the fact a property name need to be a variable, meaning you can’t have more than one private property having a certain name at the same time (or you need to use an alternative like aliasing, which is confusing).

I’ve found a good solution to this problem : a ‘private’ property still has a String name, but the property can be accessed only if the current context has the right ‘private’ key defined.

For example:
function A() {
private privateProp;
// Similar to ==> var privateProp = new PrivateProperty(); RegisterPrivateProperty(privateProp);
private this.privateProp = privateProp; // the PropertyDescriptor of this, 'privateProp' will turned in something like { ...., privateKey: privateProp }
this.privateProp = true; alert(this.privateProp); // succeed, because the privateProp property exists and its privateKey (privateProp) is registered in the current context

// Outside the function the privateProp is not registered anymore, making the property hidden
// Similar to ==> UnRegisterPrivateProperty(privateProp);
}

var a = new A(); alert(a.privateProp) // undefined

It would be even better than the current spec in the sense it don’t make it necessarry to accept a new type of value for property indexes, and it doesn’t have the problem of mixed names. It also make a single ‘private key’ reusable for more than one property, which is great.

Since the PrivatePropertyRegister is only a sort of array, it can contains multiple private property key, and the only thing to do when a property is read is to see if the ‘privateKey’ descriptor is different from undefined and, in such case, see if that key is currently in the PrivatePropertyRegister. If not, the read operation would return undefined, as if the property didn’t exists. Same when we try to modify the property.

private key1; var a, b, c;
if (true) {
private key2;
a = { private key: key1, key:'value' };
b = { private key: key2, key:'value'; };
c = { private myProp: key2, myProp:'value'; };
a.key // 'value', key1 is in the register
b.key // 'value', key2 is in the register
c.myProp // 'value'
}
a.key // 'value'
b.key // undefined, key2 is not in the register anymore
c.myProp // undefined

Previous post:

Next post: