Marcus Pope

Husband, Father, Inventor, Programmer Highlands Ranch, CO - °

Blog » 2012 » JavaScript Object Prototypes Are Verboten

A couple of weeks ago I gave a presentation at the Austin JavaScript / Austin Node.js meetup. It was a joint meetup that had a decent turnout of about 40 people. Evernote (Who knew they're in Austin now?) offered their beautiful facility for the group. I covered a topic that is almost religious at this point amongst framework developers – whether or not extending the Object.prototype chain is an acceptable practice in JavaScript. I half-expected a flamewar, and half-expected public mockery. The result was a somewhat dry technical presentation followed by a very engaged Q&A period after the speech that lasted into the evening.

Angus Croll, a core web developer at Twitter was right when he said, “I won't tell you never to augment Object.prototype but I will tell you that doing so will make you a social pariah.” I've met nothing but bad excuses and personal disgust in bug ticket forums while trying to request bug fixes in various JavaScript libraries. jQuery recently made it final that they wouldn't implement proper for..in loops – a discussion that started back in 2006 that I resurrected with real earnest back in 2009. Back then John Resig was on my side and wanted to make jQuery compatible with any framework. Unfortunately even he succumbed to the pressures of the internet and they never patched jQuery. 4 years later, the same tired excuses still hold up – excuses that weren't even justified 10 years ago and are certainly not an issue today.

What are Object.prototype extensions?

In a nutshell functions and properties added to the prototype of Object in JavaScript expose those functions to every object you create or encounter in scripting host. And only one instance of the function is created, whereas Object.extend() will duplicate the function for every object you extend.

For a more detailed and accurate explanation, you should pick up a copy of “JavaScript: The Definitive Guide” by O'Reilly which devotes a couple chapters to the subject.

Why are they so bad?

Here is a general list of excuses you'll hear about why they're bad:

  1. They break for..in loops
  2. They destroy performance
  3. People could overwrite .hasOwnProperty and break code
  4. If everyone did it there would be incompatible implementations
  5. They make code more difficult to read
  6. You don't need them, so don't use them

#1 & #2 are the only real concerns in the list. #3 & #4 are just general problems with programming in JavaScript, not something inherent to Object prototypes. People could overwrite undefined - does that mean we should never use undefined checks? People can overwrite $ (and many do) does that mean we should never use public namespace aliases? #4 can specifically be seen with Function.prototype.bind implementations but that didn't stop it from becoming a standard in ES5.

#5 used to be the same complaint against chaining in jQuery, now it's a common pattern that most people prefer. #6 – well technically you don't need semicolons ending every line, while statements, try-catch constructs, dynamic argument arrays, first class function objects, Array prototype extensions like map or forEach, and about a thousand other features of the JavaScript language. We could strip the language down to scalars, if-blocks, and for-loops and not need a single other feature to write functioning code.

But What About Performance & For..In Loops?

Let's start with for..In loops since the performance criticism is a consequence to issue #1. The reason people think Object prototype extensions break for..in loops is because they believe JavaScript for..in loops should behave like every other language's for..in loops. (Un?)fortunately this is not the case. With JavaScript you must use .hasOwnProperty() checks against every property to implement a for..in loop similar to PHP, Ruby, Python, Perl, C# etc.

JavaScript is a prototypal language where Objects are both your structure definition and your data. Other languages, including those above, rely on two separate concepts - Classes to define structure and Objects to define data. As a result when you need to implement reflection on JavaScript objects, you don't need special constructs like PHP's ReflectionClass::getMethods, or C#'s Type & SystemReflection.Assembly libraries, you just use the same for..in construct and check if the key is a local or inherited property on the prototype chain. So if you need to reflect on methods defined on the inheritance chain in JavaScript you only need the for..in loop, .hasOwnProperty() and typeof x == function, versus the five different ways of doing so in C#.

In JavaScript if you write for..in loops to iterate over the data of an object, without using .hasOwnProperty() checks, you have a bug in your code. Some people really don't like that statement as it implies that jQuery, Prototype, ExpressJS, and many other libraries have bugs in their code. Their responses tend to be that it's extending the Object prototype that is the bug, and that if you don't do that the code will run correctly. That's like saying if you don't run this code after the year 2000, it will run correctly, so the Y2K bug isn't really a bug. Just because the conditions for causing the bug are not aligned does not mean the bug isn't a bug... perhaps this is the case if you're an existentialist programmer living in 1999.

Just like try..catch blocks and argument arrays, Object.prototype extensions are very valid and useful constructs in JavaScript programming. They are well defined in the language specification and well supported in just about every JavaScript host in use today. Excluding a very rare and fixable bug in Microsoft's implementation of JScript before IE9, the Object prototype is completely safe to extend.

So They Don't Break Code, But .hasOwnProperty() Checks in Every For Loop Would Kill My Performance!

This wasn't even the case in 2006, let alone 6 years later (read: technological millennia) with GPU enabled graphics and multi-core processors and modern interpreter enhancements like Google Chrome's V8 engine. Not only have .hasOwnProperty checks become extremely efficient, for loops are rarely the bottleneck in your code. When you have extensions on the Object.prototype chain using .hasOwnProperty will actually improve your performance by reducing the amount of work per iteration on an object – Prototype's Object.extend() could benefit from this fix.

But even when you don't have extensions, and even if your application was 99% for loops, the performance loss you'll see when you add the extra if statement will be less than the average performance delta between the top 4 browsers on the market. So if your application cannot handle adding a .hasOwnProperty check to every for..in loop in your codebase, then your application could never be used in Safari, which has the slowest for..in loop performance of all browsers. And these operations are by no means slow - being able to process 30 – 120k operations per second is an abundant amount of processing speed by comparison to things like DOM operations, String interpolation, and anything related to AJAX.

If They're Not Forbidden, Then Why Should We Use Them?

The benefit of their use is a topic rarely discussed because the conversation is cut short by those with a catchy phrase. In reality Object prototype extensions serve a number of great purposes. They significantly cut down on the memory foot print of objects by only declaring one instance of a method regardless of how many objects created. They create chainable function syntax which reads from left to right like jQuery & the English language – whereas function nesting reads right to left from an execution sequence perspective. Object prototype extensions eliminate the need to wrap objects – no longer do we need to add var $elem = $(this) to every for loop like we do today with jQuery (talk about a real performance killer!) And we can even avoid ever having to type .hasOwnProperty checks on every for..in loop by writing an .each iterator function on the Object.prototype chain.

//js
Object.prototype.each = function(f) {
    var res = [];
    for (key in this) {
        if (this.hasOwnProperty(key)) {
            res.push(f.apply(
                this,
                [this[key], key].concat([].splice.call(arguments, 1))
            ));
         }
     }
    return res;
};

(This code sample is not cross-javascript-host compatible, merely an educational reference.)

Another example of their strength is with respect to Node.js's tendency to pass an error object back as the first parameter to callback functions. In Node.js it is very common to see this:

//js
SomeAsyncFunction('action', function(err, success) {
    if (err) {
        global_err_handler(err, “message”);
    }
    else {
        //use success object
    }
});

This coding pattern is pretty verbose, and many libraries don't even create a common err handler function they just re-create error handling logic inline throughout the codebase. With Object.prototype extensions you can do this instead:

//js
Object.prototype.toss = function(msg) {
    return this;
};

Error.prototype.toss = function(msg) {
    If (global_err_handler(this, msg)) throw this;
};

Which means you can then do this in your code instead:

//js
SomeAsyncFunction('action', function(err, success) {
    If (err) err.toss();
    //use success object
});

Going one step further, we no longer need to have two separate parameters for the callback. Now we can just use one parameter and rely on polymorphism to do what's right:

//js
SomeAsyncFunction('action', function(poly) {

    poly.toss(); //will throw an exception if it's an error.

    poly.ea(...); //will process only if poly is a regular object
});

When the error object is an actual error, toss() the grenade and let it explode. Otherwise, it's a false alarm and we can just move on. This type of polymorphism at the raw data level gives you classical inheritance patterns on every piece of data you work with, not just special pieces of data you've previously wrapped in a special object by duplicating the memory footprint of said object. And abstracting the control of error handling logic to the Error object means you can control which exceptions get thrown using dependency injection instead of disparate lines of code sprinkled throughout the codebase.

I'm Still Not Convinced, How Can I Be Sure They're Safe?

If you're dead set on the “Object Prototype Extensions Are Verboten” camp I'd love to hear from you. It's quite possible that I've only spoken with people and found research online from those who make weak arguments against Object prototype extensions. But in the 6 years that I've used my framework Verboten.js (formerly called Q) I have yet to come across a problem with extending the Object prototype from a language perspective. That doesn't mean I don't run into bugs in other libraries because they use JavaScript reflection for data iteration. But step one is to question the merits of such catchphrases. Once we can surpass that hurdle the real benefits of Object prototype extensions will start to flourish as people begin to explore deeper into the prototypal patterns offered by JavaScript, which are unlike most other popular programming languages around today.