"JavaScript: The Good Parts", built-in object augmentation and namespacing

I've read and am now rereading Douglas Crockford's JavaScript: The Good Parts. It is a thought provoking book. I think he has achieved one of his stated goals to "start you on process of discovering the ways those components can be put together" where "those" refers to the good parts of JavaScript. If you have followed Douglas' writing online and watched his videos you will likely be familiar with the majority of information. Either way I recommend the book to reflective programmers who appreciate the form of their code as much as the products they are building.

The one part of the book that makes me cringe is Douglas' augmentation of built-in prototypes. He provides multiple examples like defining String.prototype.trim. This strikes me as extremely reckless and creates brittle JavaScript in it's most common environment, the browser, where, as time passes, it is decreasingly likely a single developer or company writes all the code on a web page. If multiple developers are augmenting built-in objects then adding String.prototype.trim is very likely to cause a collision. Douglas has expounding many times on his enthusiasm for mashups where this type of collision is most likely and which doubles the perplexity of his practice of augmenting built-in objects.

I've challenged Douglas about this augmentation practice on the JSON mailing list because in his json2.js program he augments Date.prototype.toJSON. This augmentation appears completely gratuitous. Rewriting the json2.js file to not use augmentation is trivial and results in a more resilient program. Douglas' response to my challenge was

"Augmentation of the core objects is one of the places where we can effectively repair the language. It should not be done carelessly. I think this is a good use. It will likely be built into the next edition of the standard."

I don't think the language is anywhere near "broken" in this case. It is simply missing a toJSON method. His response also recalls a problem the Prototype.js library developers have run into causing great grief. When a method is added to a JavaScript implementation, the then native method and the previously user defined method may not be exactly the same which breaks programs. Real cases of this problem have occurred. We need to learn from history and improve our practices accordingly.

Douglas' use of augmentation seems antithetical to one of his books main themes of "global abatement" to avoid collisions in the global namespace. One of his global abatement techniques is a common one where a single global object is used as a pseudo namespace like MYAPP, YAHOO, dojo. Douglas describes that using a global object this way "significantly reduces the chance of bad interactions with other applications, widgets or libraries." Yet he causes almost certain bad interaction by augmenting String.prototype.trim!? This apparent contradiction comes from an author who is ostensibly describing the "right" way to write professional JavaScript programs. Can this contradiction be reconciled?

Just before purchasing Douglas' book, I had been thinking about JavaScript namespacing. It occurred to me that safe namespacing must occur in two dimensions when defining names in any public space. The name needs to involve who you are because that should be unique in the world. The name also needs to involve what you are doing accurately because you should only be doing whatever that is once in your entire code base. So a safe global name in a browser event library would be ca_michaux_addListener where the ca_michaux part partitions all my names away from everyone else's. For Dojo they are relatively safe with dojo.connect for the same job.

In browser scripting, we define names in many different spaces as the browser is effectively a framework and we attach names where needed. For example, if we create an HTML form with a name attribute then we are augmenting the document.forms object. For safe namespacing, it would be prudent for me to give my forms names like ca_michaux_loginForm. It is verbose but it is safer. The main point is that we do define names in more spaces than just JavaScript's global object.

So if we are already augmenting multiple JavaScript objects, what makes augmenting built-in prototypes so inherently bad? The answer may be "nothing". There is no real difference between augmenting document.forms than there is augmenting String.prototype, is there? (Augmenting Object.prototype is still bad because it breaks for in loops and using hasOwnProperty inside these loops does not fix all the problems.)

The common issue with augmenting the JavaScript global object, document.forms, or String.prototype is these are public spaces and we need to augment by defining names with care to avoid collisions. If I define my trim function as String.prototype.ca_michaux_trim then, for practical purposes, that should be safe from a collision. It should be just as safe as if I define a global function ca_michaux_trim.

We certainly cannot claim our programs are safe if we just go around willy-nilly defining generic names in public namespaces. Douglas may not be wrong that augmenting built-in prototypes is ok but his examples are not doing it in a responsible and resilient manner.

Even with my gripe here, Douglas' book is great and I agree with the majority of the ideas he expresses. I think the type of programmers who would read through any of my blog articles about the JavaScript language would enjoy his writing.

Comments

Have something to write? Comment on this article.

Karl Krukow June 18, 2008

You say:

(Augmenting Object.prototype is still bad because it breaks for in loops and using hasOwnProperty inside these loops does not fix all the problems.)

I was wondering what problems 'hasOwnProperty' does not fix?

-Karl

Peter Michaux June 18, 2008

Karl,

Suppose a child object object inherits from a parent object. If you for-in loop over the child object's properties without hasOwnProperty then the properties augmented onto Object.prototype as well as the child and parent object are enumerated. If you use hasOwnProperty then only the child properties are enumerated. What if you want to enumerate the child and parent properties of the child object? The only way to leave this possibility open is to not augment Object.prototype and to not use hasOwnProperty.

Matt Snider June 18, 2008

I like augmenting native objects, because it makes for simpler and less verbose code. This is actually one the reasons I like JavaScript so much as a language.

If you don't augment native objects, then you have to create and manage utility function/object for each function you might otherwise attach to the JavaScript native object. With the exception of Object (and Array, if you still use associative arrays).

You are right on about the namespace issue. Not only does it keep methods from overwriting each other, but also, if we came up with a good convention, such as FirstInitial+MiddleInitial+''LastName''+MethodName, then we would have an idea about the origins of a method.

Convention for a trim method written by me, would then be:

String.prototype.me_snider_trim = function() {/** logic */};

Peter Michaux June 18, 2008

Matt,

The convention resulting in String.prototype.ca_michaux_trim is based on the fact that there is an internet domain registry and I have made an agreement with them that I have exclusive rights to using domains ending in michaux.ca. If such a complete solution is desired then a central registration is necessary. With your system based on a person's name there would be a collision with two namesakes developing JavaScript.

I used to think such a complete solution was overly verbose but really it isn't very verbose at all in the case of global objects.

var ca_michaux;

(function() {
  var ns = ca_michaux;
  ns.addListener = function(){};
})();

In the case of built-in object augmentation or form names, I don't think there is a lesser verbose solution which is as complete. A central JavaScript namespace registry would allow Dojo to officially reserve dojo so they could write String.prototype.dojo_trim rather than needing to write String.prototype.org_dojotoolkit_trim, for example. Such a central registry is probably a pipe dream because it would not be required that developers register.

Andrew Hedges June 18, 2008

Hey Peter,

I remember that exchange from the JSON mailing list. Douglas' response did strike me as a bit inconsistent. I guess he's in a position to influence the process to the degree that he can be confident that toJSON will be added to the Date object in the next version of ECMAScript, but for me, a humble web developer, I prefer to wait until the method is actually available before striking out in this way.

-Andrew

Karl Krukow June 19, 2008

Hi again,

Regarding:

Suppose a child object object inherits from a parent object. If you for-in loop over the child object's properties without hasOwnProperty then the properties augmented onto Object.prototype as well as the child and parent object are enumerated.

I see you point. However, I would argue that even if you avoid augmenting Object.prototype you would still run into this problem. Suppose you have now three objects child < parent < grandparent (where < denotes inheritance). Now suppose you still only want the properties of child and parent; your for-in loop would include also properties of grandparent, and you'd need to use hasOwnProperty anyway. So I do think using for-in with hasOwnProperty is the way to go, and with this pattern augmenting Object.prototype is ok.

Now, with that said I think the main reason for not augmenting Object.prototype is that it breaks many JavaScript libraries that your users may be using.

Regards,
/Karl

Peter Michaux June 19, 2008

Karl,

You are right that the grandparent properties would be iterated if the child and parent properties are desired; however, that is probably not a very common situation. Not as common as what I'm suggesting where you have an object you want to inspect and don't even know the inheritance levels. You just want to see all the properties other than those on Object.prototype. The point is somewhat moot...

I spent more time thinking about this issue after I replied to your comment. If my code is going to be running in an environment with other people's code then it is safer to assume they are augmenting Object.prototype and I should always use a nested hasOwnProperty inside my for in loops. This limits what I can do with a for in loop but still allows the ways I actually do use them.

I was also thinking that even if I do use hasOwnProperty inside loops that I shouldn't augment Object.prototype as that may break someone else's code. So I agree with you about this issue.

I'm still not completely decided if it really is ok or necessary to augment other built-in prototype objects even with relatively safe names like String.prototype.ca_michaux_trim. I have been getting by fine without this practice and reducing the number of global spaces I use seems safer than increasing the number of spaces.

The one thing I am sure about is augmenting String.prototype.trim is not safe and two developers may have different ideas what this generically names function should do.

shiriru June 27, 2008

Is there any problem if you extend the native objects by using object based namespacing? I think one should always put all its code within a namespace but also offer a way to import function to the global namespace. Like so (don't bother checking, errors to stay short...)

var JAME = {};

JAME.import = function(obj,func) {
   var native = obj.prototype;
   var me     = native.JAME;
   if(func==='*') {
      for(var f in me) {
        native[f] = me[f];
      }
      return;
   }
   native[func] = me[func];
}


String.prototype.JAME = {};

String.prototype.JAME = {
  trim : function() {
    alert('trim trim');
  },
  bleach : function() {
    alert('^^;')
  }
  //go on here
};

'string'.JAME.trim();


JAME.import(String,'trim');//lazy me!
'string'.trim();
JAME.import(String,'*');//I knew I could be even lazier!
'string'.bleach();

therefore you keep full integrity of the client environment but give a way to shortcut things when he/she knows it's possible. give possibility to import don't export otherwise

Peter Michaux June 27, 2008

shiriru,

You can do something approximately like you are suggesting but I think there is no need to augment String.prototype to accomplish it. Just keep the namespaced stuff in the global JAME does the same thing and is less confusing.

One problem with your suggestion is the this object will not be correct when augmenting the built-in prototypes.

The following line in your example works with your alert but won't work if you really want to have a reference to the string because the this object when trim runs will be JAME.

'string'.JAME.trim();

When you import you probably want to have lines like

JAME.import(String.prototype, 'trim');
shiriru June 27, 2008

oh yeah, you're right^^; my bad, I've totally forgotten this... once imported the this get back to the native object but it doesn't bring that much and in that case, as you said, keeping everything clean within one namespace is a much more consitent way of doing it.

I've never extended native objects, I know why know!

Brad Fults July 13, 2008

I think the namespacing and collision concerns really come down to what type of code you are writing: application or library. If you are writing a library, you should take great care to ensure that your code will interoperate with as many other libraries and applications as is feasible. This often means verbose naming, careful augmentation of existing objects and/or a single global object namespace.

In the world of application development, however, I don't see nearly the same need for restraint. I think an application developer should use whatever reasonable means necessary to ensure a clear and functioning application; concerns about object collisions and such are definitively second place, precisely because the application developer controls the environment and the code that is being executed.

None of that is to say that one shouldn't adhere to hygienic practices like hierarchical namespaces and modules when appropriate (strangely, a lot of your code seems to sit out in the open global scope, with very long and messy camelCased names), but just that application developers should, in general, only have to worry about stepping on their own toes.

Ruby development seems to adhere to this same philosophy of application developers augmenting native objects when it makes application code clearer. Library code, however, is best left rolling its own solutions in tightly controlled namespaces. The end result is that application code feels very tightly integrated with the language and terse expressions become very powerful and intuitive for a developer used to the same idioms.

Peter Michaux July 13, 2008

Brad,

With regard to avoiding namespace collisions, there is no more collision protection provided by ns1.ns2.fn than ns1_ns2_fn or ns1Ns2Fn. None of these "sit out in the global scope" more than the others.

MillsJROSS July 16, 2008

I think there definitely needs to be care used when deciding to attach things on the native object. However, adding my own namespace like ca_michaux_trim, just doesn't sit well with me. It's not even that it's verbose. It's more that I'll have, essentially, repeating trim functions. Even if my trim function runs slightly different from a libraries interpretation, I should be getting back the same result. So why not just let them collide? It'll take up less running memory, so the application should run faster.

I'm not suggesting we do this for every function, but trim is so basic and so frequently used that it's implementation is understood. I put in a string, I get out a trimmed string. If, as a developer, I want to do something fancier then that I should then use a namespace or build it as a utility function rather than on the native object.

There are a few functions that are implemented to work the same in so many libraries, that it's silly not to just call them the same thing. For the most part I agree, however, that using a namespace for lesser known functions is a good idea.

Brad Fults July 16, 2008

With regard to avoiding namespace collisions, there is no more collision protection provided by ns1.ns2.fn than ns1_ns2_fn or ns1Ns2Fn. None of these "sit out in the global scope" more than the others.

Syntactically, you're correct-the punctuation used to namespace is entirely irrelevant. Technically and conceptually, though, foo.bar = 1; foo.baz = 2; has one symbol in the global scope (foo), whereas foo_bar = 1; foo_baz = 2; has two symbols in the global scope. In either case, it's tangential to my central point.

Do you have any thoughts on my distinction between application code and library code?

Peter Michaux July 16, 2008

Brad,

Yes technically foo_bar and foo_baz have two symbols in the global namespace but I no longer see much conceptual difference between these and foo.bar and foo.baz. The concept is to avoid namespace collisions and both accomplish this. Using a single global object foo to accomplish this goal is just one way to do it and other than that the fact it is a single global object doesn't provide much.

I understand your point about library vs. application code. In one sense it seems that application code should not need to be as paranoid about namespace collisions because it is the top level of abstraction, not to be reused elsewhere. Thinking in terms of bottom up programming, however, there is no one distinct boundary between library and application code. Instead there are many layers with each building on the one below. That means the application layer is just a very thin layer at the top and so worrying about namespacing is not much of a burden because the layer is so thin. Perhaps only a few symbols need to have some prefix added. Also bottom up programming makes me think that another higher layer might always be needed so why not keep the namespace clean at every level for consistency?

Have something to write? Comment on this article.