Perfection Kills

by kangax

Exploring Javascript by example

What’s wrong with extending the DOM

April 5th, 2010

I was recently surprised to find out how little the topic of DOM extensions is covered on the web. What’s disturbing is that downsides of this seemingly useful practice don’t seem to be well known, except in certain secluded circles. The lack of information could well explain why there are scripts and libraries built today that still fall into this trap. I’d like to explain why extending DOM is generally a bad idea, by showing some of the problems associated with it. We’ll also look at possible alternatives to this harmful exercise.

But first of all, what exactly is DOM extension? And how does it all work?

How DOM extension works

DOM extension is simply the process of adding custom methods/properties to DOM objects. Custom properties are those that don’t exist in a particular implementation. And what are the DOM objects? These are host objects implementing Element, Event, Document, or any of dozens of other DOM interfaces. During extension, methods/properties can be added to objects directly, or to their prototypes (but only in environments that have proper support for it).

The most commonly extended objects are probably DOM elements (those that implement Element interface), popularized by Javascript libraries like Prototype and Mootools. Event objects (those that implement Event interface), and documents (Document interface) are often extended as well.

In environment that exposes prototype of Element objects, an example of DOM extension would look something like this:


  Element.prototype.hide = function() {
    this.style.display = 'none';
  };
  ...
  var element = document.createElement('p');

  element.style.display; // ''
  element.hide();
  element.style.display; // 'none'

As you can see, “hide” function is first assigned to a hide property of Element.prototype. It is then invoked directly on an element, and element’s “display” style is set to “none”.

The reason this “works” is because object referred to by Element.prototype is actually one of the objects in prototype chain of P element. When hide property is resolved on it, it’s searched throughout the prototype chain until found on this Element.prototype object.

In fact, if we were to examine prototype chain of P element in some of the modern browsers, it would usually look like this:


  // "^" denotes connection between objects in prototype chain

  document.createElement('p');
    ^
  HTMLParagraphElement.prototype
    ^
  HTMLElement.prototype
    ^
  Element.prototype
    ^
  Node.prototype
    ^
  Object.prototype
    ^
  null

Note how the nearest ancestor in the prototype chain of P element is object referred to by HTMLParagraphElement.prototype. This is an object specific to type of an element. For P element, it’s HTMLParagraphElement.prototype; for DIV element, it’s HTMLDivElement.prototype; for A element, it’s HTMLAnchorElement.prototype, and so on.

But why such strange names, you might ask?

These names actually correspond to interfaces defined in DOM Level 2 HTML Specification. That same specification also defines inheritance between those interfaces. It says, for example, that “… HTMLParagraphElement interface have all properties and functions of the HTMLElement interface …” (source) and that “… HTMLElement interface have all properties and functions of the Element interface …” (source), and so on.

Quite obviously, if we were to create a property on “prototype object” of paragraph element, that property would not be available on, say, anchor element:


  HTMLParagraphElement.prototype.hide = function() {
    this.style.display = 'none';
  };
  ...
  typeof document.createElement('a').hide; // "undefined"
  typeof document.createElement('p').hide; // "function"

This is because anchor element’s prototype chain never includes object refered to by HTMLParagraphElement.prototype, but instead includes that referred to by HTMLAnchorElement.prototype. To “fix” this, we can assign to property of object positioned further in the prototype chain, such as that referred to by HTMLElement.prototype, Element.prototype or Node.prototype.

Similarly, creating a property on Element.prototype would not make it available on all nodes, but only on nodes of element type. If we wanted to have property on all nodes (e.g. text nodes, comment nodes, etc.), we would need to assign to property of Node.prototype instead. And speaking of text and comment nodes, this is how interface inheritance usually looks for them:


  document.createTextNode('foo'); // < Text.prototype < CharacterData.prototype < Node.prototype
  document.createComment('bar'); // < Comment.prototype < CharacterData.prototype < Node.prototype

Now, it's important to understand that exposure of these DOM object prototypes is not guaranteed. DOM Level 2 specification merely defines interfaces, and inheritance between those interfaces. It does not state that there should exist global Element property, referencing object that's a prototype of all objects implementing Element interface. Neither does it state that there should exist global Node property, referencing object that's a prototype of all objects implementing Node interface.

Internet Explorer 7 (and below) is an example of such environment; it does not expose global Node, Element, HTMLElement, HTMLParagraphElement, or other properties. Another such browser is Safari 2.x (and most likely Safari 1.x).

So what can we do in environments that don't expose these global "prototype" objects? A workaround is to extend DOM objects directly:


  var element = document.createElement('p');
  ...
  element.hide = function() {
    this.style.display = 'none';
  };
  ...
  element.style.display; // ''
  element.hide();
  element.style.display; // 'none'

What went wrong?

Being able to extend DOM elements through prototype objects sounds amazing. We are taking advantage of Javascript prototypal nature, and scripting DOM becomes very object-oriented. In fact, DOM extension seemed so temptingly useful that few years ago, Prototype Javascript library made it an essential part of its architecture. But what hides behind seemingly innocuous practice is a huge load of trouble. As we'll see in a moment, when it comes to cross-browser scripting, the downsides of this approach far outweigh any benefits. DOM extension is one of the biggest mistakes Prototype.js has ever done.

So what are these problems?

Lack of specification

As I have already mentioned, exposure of "prototype objects" is not part of any specification. DOM Level 2 merely defines interfaces and their inheritance relations. In order for implementation to conform to DOM Level 2 fully, there's no need to expose those global Node, Element, HTMLElement, etc. objects. Neither is there a requirement to expose them in any other way. Given that there's always a possibility to extend DOM objects manually, this doesn't seem like a big issue. But the truth is that manual extension is a rather slow and inconvenient process (as we will see shortly). And the fact that fast, "prototype object" -based extension is merely somewhat of a de-facto standard among few browsers, makes this practice unreliable when it comes to future adoption or portability across non-convential platforms (e.g. mobile devices).

Host objects have no rules

Next problem with DOM extension is that DOM objects are host objects, and host objects are the worst bunch. By specification (ECMA-262 3rd. ed), host objects are allowed to do things, no other objects can even dream of. To quote relevant section [8.6.2]:

Host objects may implement these internal methods with any implementation-dependent behaviour, or it may be that a host object implements only some internal methods and not others.

The internal methods specification talks about are [[Get]], [[Put]], [[Delete]], etc. Note how it says that internal methods behavior is implementation-dependent. What this means is that it's absolutely normal for host object to throw error on invocation of, say, [[Get]] method. And unfortunatey, this isn't just a theory. In Internet Explorer, we can easily observe exactly this—an example of host object [[Get]] throwing error:


  document.createElement('p').offsetParent; // "Unspecified error."
  new ActiveXObject("MSXML2.XMLHTTP").send; // "Object doesn't support this property or method"

Extending DOM objects is kind of like walking in a minefield. By definition, you are working with something that's allowed to behave in unpredictable and completely erratic way. And not only things can blow up; there's also a possibility of silent failures, which is even worse scenario. An example of erratic behavior is applet, object and embed elements, which in certain cases throw errors on assignment of properties. Similar disaster happens with XML nodes:


  var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
  xmlDoc.loadXML('bar');
  xmlDoc.firstChild.foo = 'bar'; // "Object doesn't support this property or method"

There are other cases of failures in IE, such as document.styleSheets[99999] throwing "Invalid procedure call or argument" or document.createElement('p').filters throwing "Member not found." exceptions. But not only MSHTML DOM is the problem. Trying to overwrite "target" property of event object in Mozilla throws TypeError, complaining that property has only a getter (meaning that it's readonly and can not be set). Doing same thing in WebKit, results in silent failure, where "target" continues to refer to original object after assignment.

When creating API for working with event objects, there's now a need to consider all of these readonly properties, instead of focusing on concise and descriptive names.

A good rule of thumb is to avoid touching host objects as much as possible. Trying to base architecture on something that—by definition—can behave so sporadically is hardly a good idea.

Chance of collisions

API based on DOM element extensions is hard to scale. It's hard to scale for developers of the library—when adding new or changing core API methods, and for library users—when adding domain-specific extensions. The root of the issue is a likely chance of collisions. DOM implementations in popular browsers usually all have properietary API's. What's worse is that these API's are not static, but constantly change as new browser versions come out. Some parts get deprecated; others are added or modified. As a result, set of properties and methods present on DOM objects is somewhat of a moving target.

Given huge amount of environments in use today, it becomes impossible to tell if certain property is not already part of some DOM. And if it is, can it be overwritten? Or will it throw error when attempting to do so? Remember that it's a host object! And if we can quietly overwrite it, how would it affect other parts of DOM? Would everything still work as expected? If everything is fine in one version of such browser, is there a guarantee that next version doesn't introduce same-named property? The list of questions goes on.

Some examples of proprietary extensions that broke Prototype are wrap property on textareas in IE (colliding with Element#wrap method), and select method on form control elements in Opera (colliding with Element#select method). Even though both of these cases are documented, having to remember these little exceptions is annoying.

Proprietary extensions are not the only problem. HTML5 brings new methods and properties to the table. And most of the popular browsers have already started implementing them. At some point, WebForms defined replace property on input elements, which Opera decided to add to their browser. And once again, it broke Prototype, due to conflict with Element#replace method.

But wait, there's more!

Due to long-standing DOM Level 0 tradition, there's this "convenient" way to access form controls off of form elements, simply by their name. What this means is that instead of using standard elements collection, you can access form control like this:


  <form action="">
    <input name="foo">
  </form>
  ...
  <script type="text/javascript">
    document.forms[0].foo; // non-standard access
    // compare to
    document.forms[0].elements.foo; // standard access
  </script>

So, say you extend form elements with login method, which for example checks validation and submits login form. If you also happen to have form control with “login” name (which is pretty likely, if you ask me), what happens next is not pretty:


  <form action="">
    <input name="login">
    ...
  </form>
  ...
  <script type="text/javascript">
    HTMLFormElement.prototype.login = function(){
      return 'logging in';
    };
    ...
    $(myForm).login(); // boom!
    // $(myForm).login references input element, not `login` method
  </script>

Every named form control shadows properties inherited through prototype chain. The chance of collisions and unexpected errors on form elements is even higher.

Situation is somewhat similar with named form elements, where they can be accessed directly off document by their names:


  <form name="foo">
    ...
  </form>
  ...
  <script type="text/javascript">
    document.foo; // [object HTMLFormElement]
  </script>

When extending document objects, there’s now an additional risk of form names conflicting with extensions. And what if script is running in legacy applications with tons of rusty HTML, where changing/removing such names is not a trivial task?

Employing some kind of prefixing strategy can alleviate the problem. But will probably also bring extra noise.

Not modifying objects you don’t own is an ultimate recipe for avoiding collisions. Breaking this rule already got Prototype into trouble, when it overwrote document.getElementsByClassName with own, custom implementation. Following it also means playing nice with other scripts, running in the same environment—no matter if they modify DOM objects or not.

Performance overhead

As we’ve seen before, browsers that don’t support element extensions—like IE 6, 7, Safari 2.x, etc.—require manual object extension. The problem is that manual extension is slow, inconvenient and doesn’t scale. It’s slow because object needs to be extended with what’s often a large number of methods/properties. And ironically, these browsers are the slowest ones around. It’s inconvenient because object needs to be first extended in order to be operated on. So instead of document.createElement('p').hide(), you would need to do something like $(document.createElement('p')).hide(). This, by the way, is one of the most common stumbing blocks for beginners of Prototype. Finally, manual extension doesn’t scale well because adding API methods affects performance pretty much linearly. If there’s 100 methods on Element.prototype, there has to be 100 assignments made to an element in question; if there’s 200 methods, there has to be 200 assignments made to an element, and so on.

Another performance hit is with event objects. Prototype follows similar approach with events and extends them with a certain set of methods. Unfortunately, some events in browsers—mousemove, mouseover, mouseout, resize, to name few—can fire literally dozens of times per second. Extending each one of them is an incredibly expensive process. And what for? Just to invoke what could be a single method on event obejct?

Finally, once you start extending elements, library API most likely needs to return extended elements everywhere. As a result, querying methods like $$ could end up extending every single element in a query. It’s easy to imagine performance overead of such process, when we’re talking about hundreds or thousands of elements.

IE DOM is a mess

As shown in previous section, manual DOM extension is a mess. But manual DOM extension in IE is even worse, and here’s why.

We all know that in IE, circular references between host and native objects leak, and are best avoided. But adding methods to DOM elements is a first step towards creation of such circular references. And since older versions of IE don’t expose “object prototypes”, there’s not much to do but extend elements directly. Circular references and leaks are almost inevitable. And in fact, Prototype suffered from them for most of its lifetime.

Another problem is the way IE DOM maps properties and attributes to each other. The fact that attributes are in the same namespace as properties, increases chance of collisions and all kinds of unexpected inconsistencies. What happens if element has custom “show” attribute and is then extended by Prototype. You’ll be surprised, but show “attribute” would get overwritten by Prototype’s Element#show method. extendedElement.getAttribute('show') would return a reference to a function, not the value of “show” attribute. Similarly, extendedElement.hasAttribute('hide') would say “true”, even if there was never custom “hide” attribute on an element. Note that IE<8 lacks hasAttribute, but we could still see attribute/property conflict: typeof extendedElement.attributes['show'] != "undefined".

Finally, one of the lesser-known downsides is the fact that adding properties to DOM elements causes reflow in IE, so mere extension of element becomes a quite expensive operation. This actually makes sense, given the deficient mapping of attributes and properties in its DOM.

Bonus: browser bugs

If everything we’ve been over so far is not enough (in which case, you’re probably a masochist), here’s a couple more bugs to top it all of.

In some versions of Safari 3.x, there’s a bug where navigating to a previous page via back button wipes off all host object extensions. Unfortunately, the bug is undetectable, so to work around the issue, Prototype has to do something horrible. It sniffs browser for that version of WebKit, and explicitly disables bfcache by attaching “unload” event listener to window. Disabled bfcache means that browser has to re-fetch page when navigating via back/forward buttons, instead of restoring page from the cached state.

Another bug is with HTMLObjectElement.prototype and HTMLAppletElement.prototype in IE8, and the way object and applet elements don’t inherit from those prototype objects. You can assign to a property of HTMLObjectElement.prototype, but that property is never “resolved” on object element. Ditto for applets. As a result, those elements always have to be extended manually, which is another overhead.

IE8 also exposes only a subset of prototype objects, when compared to other popular implementations. For example, there’s HTMLParagraphElement.prototype (as well as other type-specific ones), and Element.prototype, but no HTMLElement (and so HTMLElement.prototype) or Node (and so Node.prototype). Element.prototype in IE8 also doesn’t inherit from Object.prototype. These are not bugs, per se, but is something to keep in mind nevertheless: there’s nothing good about trying to extend non-existent Node, for example.

Wrappers to the rescue

One of the most common alternatives to this whole mess of DOM extension is object wrappers. This is the approach jQuery has taken from the start, and few other libraries followed later on. The idea is simple. Instead of extending elements or events directly, create a wrapper around them, and delegate methods accordingly. No collisions, no need to deal with host objects madness, easier to manage leaks and operate in dysfunctional MSHTML DOM, better performance, saner maintenance and painless scaling.

And you still avoid procedural approach.

Prototype 2.0

The good news is that Prototype mistake is something that’s going away in the next major version of the library. As far as I’m concerned, all core developers understand the problems mentioned above, and that wrapper approach is the saner way to move forward. I’m not sure what the plans are in other DOM-extending libraries like Mootools. From what I can see they are already using wrappers with events, but still extend elements. I’m certinaly hoping they move away from this madness in a near future.

Controlled environments

So far we looked at DOM extension from the point of view of cross-browser scripting library. In that context, it’s clear how troublesome this idea really is. But what about controlled environments? When script is only run in one or two environments, such as those based on Gecko, WebKit or any other modern non-MSHTML DOM. Perhaps it’s an intranet application, that’s accessed through certain browsers. Or a desktop, WebKit-based app.

In that case, situtation is definitly better. Let’s look at the points listed above.

Lack of specification becomes somewhat irrelevant, as there’s no need to worry about compatibility with other platforms, or future editions. Most of the non-MSHTML DOM environments expose DOM object prototypes for quite a while, and are unlikely to drop it in a near future. There’s still a possibility for change, however.

Point about host objects unreliability also loses its weight, since host objects in Gecko or WebKit -based DOMs are much, much saner than those in MSHTML DOM. But they are still host objects, and so should be treated with care. Besides, there are readonly properties covered before, which could easily cripple the flexibility of API.

The point about collisions still holds weight. These environments support non-standard form controls access, have proprietary API, and are constantly implementing new HTML5 features. Modifying objects you don’t own is still a wicked idea and can lead to hard-to-find bugs and inconsistencies.

Performance overhead is practically non-existent, as these DOM support prototype-based DOM extension. Performance can actually be even better, comparing to, say, wrappers approach, as there’s no need to create any additional objects in order to invoke methods (or access properties) off DOM objects.

Extending DOM in controlled environment sure seems like a perfectly healthy thing to do. But even though the main problem is that with collisions, I would still advise to employ wrappers instead. It’s a safer way to move forward, and will save you from maintenance overhead in the future.

Afterword

Hopefuly, you can now clearly see all the truth behind what looks like an elegant approach. Next time you design a Javascript framework, just say no to DOM extensions. Say no, and save yourself from all the trouble of maintaining a cumbersome API and suffering unnecessary performance overheads. If on the other hand, you’re considering to employ Javascript library that extends DOM, stop for a second, and ask yourself if you’re willing to take a risk. Is ellusive convenience of DOM extension really worth all the trouble?

Update 1/12/2012: Bulgarian translation is now available

Update 3/6/2013: Polish translation is now available

Categories: annoyances, DOMLint, don'ts, ECMA-262

Comments (76)

  1. Gravatar

    Andrew Dupont said on Apr 5, 2010 @ 12:22

    Prototype didn’t overwrite document.getElementsByClassName. We wrote it before it was part of any spec. Then, when Firefox implemented it natively, we specifically avoided overwriting it. But that was part of the problem; our method returned an array, but the native method returns a NodeList. You can’t create a NodeList from JavaScript, of course. So that’s an issue of host objects having privileges that we don’t.

    You certainly don’t need to tell me how much of a headache it’s been to extend DOM nodes; I was there, man. I was in the shit. There was, and is, a huge upside: the ability to use Prototype’s element methods alongside built-in DOM methods and properties, without having to deal with any sort of wrapper. We’ll lose that with Prototype 2.0, but at least we’ll have a blank canvas on which to paint.

  2. Gravatar

    qFox said on Apr 5, 2010 @ 12:27

    Nice writeup. My personal favorite is <input … name=”submit”> to check isset($_POST['submit']). Try submitting the form through javascript when doing that :p

    I’m not sure the part on host objects was the right way to go, or at least, not as complete. Objects should still and in any event at least behave like an object. Silent errors should not be allowed (but then again, it could just as well be a getter with function(){}, so I guess there’s little to be done about that…

    I think the accepted “best practice” nowadays is feature sniffing over browser sniffing. I personally attach one property to dom elements in my framework, and my framework won’t fail if that doesn’t work (it just becomes slightly inefficient ;)).

  3. Gravatar

    Nicholas C. Zakas said on Apr 5, 2010 @ 12:29

    I’m glad to see you explain this problem so thoroughly. I’ve been saying don’t modify objects you don’t own for a while, and it’s nice to get some backup. :)

  4. Gravatar

    Rick said on Apr 5, 2010 @ 12:43

    Excellent article

  5. Gravatar

    Pete B said on Apr 5, 2010 @ 12:49

    Totally agree. One of the main reasons I decided to write my own library avoiding liberal DOM extensions

  6. Gravatar

    Øyvind Sean Kinsey said on Apr 5, 2010 @ 12:56

    A typical example of how extending the prototype of Host Objects is a source of trouble is using the native JSON object (or Crockfords json2) in any page that also hosts a script that extends Array with a toJSON method (like prototype.js does!).

    This actually causes the native JSON.stringfy method to reture invalid (!) JSON whenever there is an array present in the object that is being serialized…
    If you ask me this is a major issue as it forces all libraries that use JSON to check if there is an extension to Object/Array, and if so, use this instead of the faster (and stricter) native solution. And if they don’t do this, well, then the library is labeled as ‘one that doesn’t work’ by those that also use e.g. prototype.js.

  7. Gravatar

    Asen Bozhilov said on Apr 5, 2010 @ 13:11

    Good article Juriy.

    Example:

      var element = document.createElement('p');
      ...
      element.hide = function() {
        this.style.display = 'none';
      };
    

    That example isn’t very clever because forming circular reference pattern. The diagram looks:

    element -> hide[[Scope]] -> Global Object -> element

    And when `element’ refer host object in some environments, that mean memory leak. You can observe that especially in IE < 8.

    The diagram that examine prototype chain looks reversed. Of course that is my opinion, but I ever do inheritance diagram, which looks like inverse tree.
    For example:

                                             root
                                   ____________|____________
                                  |                         |
                                  |                         |
                                child1                    child2
                                  |                         |
                            ______|______             ______|______
                           |             |           |             |
                           |             |           |             |
                      subchild1.1    subchild1.2  subchild2.1   subchild2.2
    
  8. Gravatar

    Nick Stakenburg said on Apr 5, 2010 @ 13:13

    Nice writeup. I think the DOM is just part of the problem, the other ‘mistake’ Prototype made was extending everything else.

  9. Gravatar

    unscriptable said on Apr 5, 2010 @ 13:33

    @kangax and NCZ:

    At first I didn’t agree with you guys. I think it was your choice of the word “modify”. In my head, you guys were really talking about “extend”. I went back and looked at the examples in your articles. For most of these, you’re indeed talking about modifying objects, rather than extending them (via prototype chaining, for instance). Therefore, I totally agree, of course.

    However, some of these cases are truly extension and not modification. For example: kangax’s Element.prototype.hide example.

    There’s absolutely nothing wrong with this if you can trust your API. It seems to me that the mistake made by the prototype guys was placing too much trust in a specification that wasn’t followed by some platforms. (I’m looking at you, Microsoft!)

    Before I extend any object or class (which I do quite often), the following questions flash through my head:

    1) How much can I trust this API not to change? (Was it even ever designated as an API?)
    2) If/when it does change, how can I protect my code?

    If I can answer these questions with some confidence, I can then gauge the risk/reward of extending the API.

    Wrappers, as you say, are an excellent way to protect your code. jQuery takes this to the extreme and creates an API that is totally un-DOM-like, imho. I’d just use some Crockford beget magic around the document, element, and form objects to create something that is closer to the native API.

    I’m looking forward to what Prototype 2.0 brings!

    – J

  10. Gravatar

    David mark said on Apr 5, 2010 @ 19:22

    Great post Kangax. If only more people would read CLJ instead of Ajaxian, the Web wouldn’t be such a disaster area.

    @Andrew

    “We’ll lose that with Prototype 2.0, but at least we’ll have a blank canvas on which to paint”

    Not sure what that means, but why not leave it blank? About the last thing the Web needs now is another Prototype. I mean, it’s going to take long enough to scrape the last one off. :)

  11. Gravatar

    voloko said on Apr 6, 2010 @ 2:26

    First, thanks for the great post.

    I feel that is is generally (not only in this post) considered that extending prototypes is BAD. Though I think this is much less a technical issue rather than a result of much better marketing on jQuery side.

    Think about it. It is not so sweet and shiny in the wrapper land. You actually have a performance penalty for creating wrappers. Each and every time you want to access wrapped methods. Consider this example.

    $('something').mouseover(function(e) {
       $(this).doSomething() // you end up creating a wrapper here for the same element over and over again.
    }
    

    You might point out that wrapper can work with more than one Element at a time. But the question is, how often do you? In my experience in most cases I work with one and only one element. The only common scenario for a group of elements is event binding. But there’s event delegation to the rescue.

    Also, as noted in the post, using wrapped methods add performance overhead for each call. Even if browser supports any particular feature natively you still pay the price of calling the wrapped method instead of native. See jQuery .css call for example. In many performance hungry scenarios I’m forced to write to $(…)[0].style directly.

    Plus wrappers can’t provide access to all and every method of the Element native API. So in most projects you have to use both native and wrapped interfaces together. And this adds complexity to your code.

    So in non-IE browsers wrappers might end up with worse performance and more complex code.

    As for name collisions (in case of prototype) I see native implementations often doing the same thing as their prototype counterparts. See the Function#bind for example.

    And browser bugs/inconsistencies are rarely an issue of an end user. After all it is up to framework developers to deal with them. And we generally don’t write frameworks too often.

    Please don’t get me wrong. I use jQuery and ukijs for most of my work. But you’d better see both sides before jumping to a conclusion. We’re moving to a world without IE6-8. And in this world extending native prototypes might not be such a bad idea.

  12. Gravatar

    Don Hopkin said on Apr 6, 2010 @ 2:38

    @David: Well put. @Andrew: Please leave it blank, no need to start from scratch.

    I wrote off the Prototype library a long time ago as having been written by people who didn’t know what they were doing, because they extended Object, when everybody knew that doing that broke JavaScript’s “foreach” loop.

  13. Gravatar

    Don Hopkins said on Apr 6, 2010 @ 2:40

    Well maybe not *everybody* knew it, but if the Prototype designers didn’t knew it, they were incompetent, and if the prototype designers did know it, they were incompetent. Don’t fuck with the programming language.

  14. Gravatar

    Matt Kruse said on Apr 6, 2010 @ 11:48

    In a controlled browser environment (ex: Firefox add-on or Greasemonkey script), using good non-colliding choices for method names (Element.xyz_hide()), I have found it useful to extend Element.prototype. For the general web, no.

    Very good summary of the issue.

  15. Gravatar

    Asen Bozhilov said on Apr 6, 2010 @ 12:24

    @voloko augmentation of host object is bad practice until ECMA-262 specified behavior of internal properties and methods for host objects.

    Wrapper in that case do more complex things, and it’s hard to catch circular reference pattern between host object and native.

    Lets suppose wrapper like that:

    function wrapper(id) {
        this.elm = document.getElementById(id);
    }
    
    wrapper.prototype = {
        constructor : wrapper,
        observe : function(type, handler) {
            this.elm[type] = handler;
        }
    };
    
    //Setup circular reference pattern
    var element = new wrapper('elm_id');
    element.observe('onclick', function() {});
    

    element refer native object. For I catch circular reference pattern in that case, I should know value of each property of that object. I should know whole implementation of that wrapper, which I don’t want to know in one stable library. During execution of observing method, will be assign reference to passed function as value of property on host object. The diagram in that case looks:

    element -> elm -> onclick.[[Scope]] -> Global Object -> element
    

    That pattern depend by implementation. In better implementation that problem should be solved, but of course that cost more complex.

    I prefer to use singleton pattern, with object which hold my helper methods. Those helper methods expect reference to object, which they manipulate. For example:

    var DOMElement = {
        /**
         * @param {HTMLElement} el
         */
        hide : function (el) {
            //...
        }
    };
    

    Of course that isn’t so cool as $('stupid css query goes here').show().hide().animate().resize(); but I like that, because the user don’t need to know implementation of my code in first place. In second place I don’t shadow circular references as wrapper pattern.

  16. Gravatar

    matthew said on Apr 6, 2010 @ 12:53

    I’m with unscriptable: it seems to me that your use of “extension” is a little confusing here. Most of your comments seem to address modifying prototypes which (I think) is universally recognized as bad practice. Actually extending DOM elements (which I would assume means prototype chaining) would be awesome…if it weren’t made impossible by browser inconsistencies (and IE’s killer JS/DOM memory leak issue).

  17. Gravatar

    taobao said on Apr 6, 2010 @ 18:44

    This is some awesome post about manipulating the dom.

  18. Gravatar

    Andrea Giammarchi said on Apr 6, 2010 @ 23:56

    Great post Kangax. If only more people would read CLJ instead of Ajaxian, the Web wouldn’t be such a disaster area.

    @David, Juriy has been often posted in Ajaxian and moreover, Ajaxian provides news from people like Juriy, me, you, and every other dev that would like to share some research/topic/news. I think you are a bit confused about our posts aim and I found your comment totally off topic.
    I have personally never suggested to extend objects in the Prototype way and I am pretty sure if there was an article about it, you were free to comment and it was posted just “to let you know about”.

    @Kangax, @Andrew, it’s nice to see you guys admitting some mistake about Prototype library and probably the most discussed part of it: “too greedy”
    I partially agree with Nick that DOM is just one problem but at the same time, we should never forget some truly nice ES5 idea comes from one of the most obtrusive libraries so, thanks for the write up and the clear analysis (and I am pretty sure this would be a nice post for Ajaxian, regardless some people idea)

    Best Regards

  19. Gravatar

    Andrea Giammarchi said on Apr 6, 2010 @ 23:58

    … ooops, @dalmaer already did it, I was simply late :D

  20. Gravatar

    Sean Hogan said on Apr 7, 2010 @ 4:03

    A good introduction to DOM prototypes, extending the DOM, and the down-sides.
    Although there are a few straw-men examples:

    1. The examples in “Host objects have no rules” are mostly irrelevant to adding methods to a DOM object.
    They are still good points, but they apply just as much to any interaction with the DOM (including wrappers).

    document.createElement('p').offsetParent; // "Unspecified error."

    Code that asks for the offsetParent of an element not in the document is already buggy – it should never make it to release. The fact that sometimes this property throws will not be relevant.

    new ActiveXObject("MSXML2.XMLHTTP").send; // "Object doesn't support this property or method"

    This is like checking for document.getElementsByTagName. If the browser doesn’t support send() on the XMLHttpRequest object then no-one will be using that browser.

    Trying to overwrite “target” property of event object
    Besides the fact that there is no reason to do this, it isn’t an example of extending the event object.

    2. “Chance of Collisions” gives examples where Prototype has chosen method names that conflict with native properties that were added later.
    - Prototype could have followed the standard practice of a vendor prefix for non-standardized DOM methods.
    - The same issue could arise when extending wrapper-objects in JS frameworks (the difference being that at least you might have some control over the version of the framework you use).

    This section also mentions that named elements can conflict with DOM extensions, ignoring the fact that they can conflict with standard DOM properties and methods too. (Try calling form.submit() when one of the inputs is named “submit”).

    Apart from that, the vast majority of warnings seem to relate IE6 & IE7, which:
    – don’t have DOM prototypes
    – hopefully will be gone soon
    – can easily be treated as noscript browsers

    Personally, I can’t wait for IE < 8 to be an unsupported scripting environment. In that case, extending the DOM will mean DOM Prototypes and much of this article won't apply (see Controlled Environments). And if you are designing a new JS framework for uptake over the next few years then you should at least consider DOM prototypes.

  21. Gravatar

    Jeremy said on Apr 7, 2010 @ 7:45

    I think it depends on the motivation behind the extension. Adding new functionality to all browsers by extending the DOM should certainly be avoided for the reasons listed in this article. Adding functionality to some browsers (non-IE browsers) to match that of another browser (IE) is where I think DOM extensions are useful and perfectly fine.

  22. Gravatar

    Ivan said on Apr 7, 2010 @ 23:12

    I should say I am a CSS guy first and ECMAScript fanboy second.

    To me not extending the DOM and the natives sounds like “never ever ever use tag name selectors in CSS”.

    But before I go on commenting, I would like to see a preview of Prototype 2, so I could comment adequatly.

  23. Gravatar

    Garrett Smith said on Apr 9, 2010 @ 2:35

    Sean Hogan wrote:

    Personally, I can’t wait for IE < 8 to be an unsupported scripting environment. In that case, extending the DOM will mean DOM Prototypes and much of this article won't apply (see Controlled Environments).

    The prototype chain for DOM objects is not defined and is implemented inconsistently. The DOM uses interface-based architecture. The beauty of this is that new interfaces may be created and implemented without causing a massive restructuring of the entire system. Although a lot of criticism is laid on the DOM, interfaces are a crucial feature that allowed it to work.

    As stated in this article, some implementations map DOM interfaces to prototype objects. Attempts at specifying the exact structure of each and every interface, sacrifice extensibility for rigid consistency. I explained more about this to Ian Hickson on WHATWG mailing list:
    http://lists.w3.org/Archives/Public/public-webapps/2009JulSep/0512.html

    The client of the object is usually concerned with what it can do. It should not be concerned with specifics about the interface hierarchy.

    Kangax wrote regarding “Controlled environments”:

    Lack of specification becomes somewhat irrelevant, as there’s no need to worry about compatibility with other platforms, or future editions.

    Conflict with future editions is only irrelevant when the version of the browser that the applications is deployed with is guaranteed not to change.

    As Andrew Dupont mentioned, PrototypeJS added document.getElementsByClassName before Gecko did, and when Gecko did, there was a conflict:

    Prototype didn’t overwrite document.getElementsByClassName. We wrote it before it was part of any spec.

    For an application that is produced for one version of a browser, there is no good reason to require it to be tied to that particular version.

    Dmitry Soshnikov and I debated this issue and I concluded that:

    The consequences [to modifying objects you don't own]:
    1) More likely to conflict with:
    a) code added by another contributor
    b) a third party library
    c) future ECMAScript specification/proposal

    2) Not as clear as to who owns that functionality or where the
    functionality is defined (“where is this method coming from?”).

    It is also possible to create a dependency cycle. It is possible that
    the modification makes what should be internal information accessible to
    other code (e.g. adding a _listeners property to functions).

  24. Gravatar

    bobince said on Apr 12, 2010 @ 10:41

    Good article.

    Prototype’s approach of extending the DOM prototypes where available and the objects themselves where not is a total disaster, and I’m glad to see it go.

    The problem is that to be compatible you have to know which methods are potentially extensions, and every time you call one you need to make sure the object is extended. In the end this is more bothersome than just grabbing a wrapper each time.

    When you make a mistake by failing to explicitly extend something you need to — and you will — you don’t find out straight away. You don’t find out because you’re probably testing on a browser that can extend the prototype, and you may not find out even on IE because sometimes, some other condition will have occurred that has already caused the Node to be extended before you get to the code that makes that assumption of the Node already being extended. That means an explosion of interaction-order and race-condition complexity where to reproduce the bug requires a particular series of closely-timed interactions.

    In the end this amplifies the negative impact of browser differences instead of smoothing them over.

  25. Gravatar

    andrew cates said on Apr 14, 2010 @ 8:21

    Excellent write up. I’ve followed Nicholas’s advice for a while now and reading this really cleared things up a lot.

  26. Gravatar

    justin said on Jun 16, 2010 @ 16:04

    Haven’t we all been using Prototype.js for 5+ years? Why did DOM Extensions suddenly become the biggest mistake of Prototype.js?

    Given the roots of the Prototype library, they make perfect sense. For a Ruby developer, it is very easy to write Prototype-style JavaScript. That is no accident.

    To say DOM Extensions are “the biggest mistake of Prototype.js” is completely illogical. They were the basis for the library from the beginning.

  27. Gravatar

    kangax (article author) said on Jun 16, 2010 @ 16:34

    @justin

    Haven’t we all been using Prototype.js for 5+ years? Why did DOM Extensions suddenly become the biggest mistake of Prototype.js?

    Nothing happened “suddenly”, of course. DOM extensions have been the root of many problems for many years (being Prototype developer for some time, I am well aware of these problems). I also mentioned it in the post.

    Given the roots of the Prototype library, they make perfect sense. For a Ruby developer, it is very easy to write Prototype-style JavaScript. That is no accident.

    Well, of course it’s no accident. Prototype was designed like this. In Ruby, it might make sense. Not so much in the world of cross-browser scripting.

    To say DOM Extensions are “the biggest mistake of Prototype.js” is completely illogical. They were the basis for the library from the beginning.

    I don’t follow this logic. If something has been somewhere from the beginning, does it mean it can’t be a mistake? DOM extensions were a design mistake, which became more and more apparent later, during library’s lifespan.

  28. Gravatar

    justin said on Jun 16, 2010 @ 18:52

    Well I just don’t completely the buy the “DOM Extensions are evil” argument. For one, why are you ignoring the other non-DOM extensions that Prototype adds? Mucking with the Array and String prototypes is OK but not the DOM? Or is that bad too, but you just didn’t want to talk about it?

    Simply put, injecting instance methods on native objects is a mighty handy way to seamlessly inject your framework into an existing language or framework. It didn’t seem frowned upon 5-6 years ago when Rails and Prototype were first making headlines, in fact, it was embraced. What we’ve had in the JavaScript community is a major shift in design patterns because everybody has read the Crockford book and bought into it wholesale (myself included). It seems unfair to go back and say “DOM Extensions was a major mistake” just because of hindsight.

    In my opinion, Prototype’s primary goal was twofold. One, to remove a bunch of boilerplate code from the JavaScript layer. Secondly, to make writing JavaScript as much like writing Ruby as possible. Without DOM Extensions, the latter would not have been possible. That is why I find your argument illogical.

    The bigger beef I have with this article is the tone. It starts off by making me feel like a dirty philistine for enjoying DOM Extensions. Using outdated browsers like IE6 and Safari 2 to make your points do not help. I have never experienced method collisions due to DOM Extensions (save for the minor Element#getElementsByClassName issue way back when), so that argument comes across as both contrived and academic. Maybe lots of people have experienced collisions, I don’t know, but I personally have not seen it in the real world.

    Sure, I get that DOM Extensions have caused some problems and completely understand why there is a strong motivation to remove them in Prototype2. Don’t let my statements confuse that point. I am very much looking forward to the new version and all that it is going to offer us. Times have changed and I am glad to see Prototype core is right there with it.

  29. Gravatar

    kangax (article author) said on Jun 16, 2010 @ 20:45

    @justin

    Well I just don’t completely the buy the “DOM Extensions are evil” argument. For one, why are you ignoring the other non-DOM extensions that Prototype adds? Mucking with the Array and String prototypes is OK but not the DOM? Or is that bad too, but you just didn’t want to talk about it?

    You must have missed chapters “host objects have no rules”, “IE DOM is a mess”, and “performance overhead” ;) All the answers are there.

    Simply put, injecting instance methods on native objects is a mighty handy way to seamlessly inject your framework into an existing language or framework. It didn’t seem frowned upon 5-6 years ago when Rails and Prototype were first making headlines, in fact, it was embraced. What we’ve had in the JavaScript community is a major shift in design patterns because everybody has read the Crockford book and bought into it wholesale (myself included). It seems unfair to go back and say “DOM Extensions was a major mistake” just because of hindsight.

    Crockford has very little to do with understanding that extending host objects is a suboptimal practice. His lessons mainly focus on core language, not the host environment. There’s no shift either. It’s simply a matter of learning from own mistakes. I’m surprised this is not clear after seeing all the evidence right here in the post.

    In my opinion, Prototype’s primary goal was twofold. One, to remove a bunch of boilerplate code from the JavaScript layer. Secondly, to make writing JavaScript as much like writing Ruby as possible. Without DOM Extensions, the latter would not have been possible. That is why I find your argument illogical.

    I am not arguing about Prototype being similar to Ruby. As I have already said, extending DOM objects was a design mistake. Things that work well in Ruby don’t always work well in Javascript. Just like things that work well in Java don’t always work well in Javascript. Extending DOM objects is elegant in theory, but ugly in practice.

    The bigger beef I have with this article is the tone. It starts off by making me feel like a dirty philistine for enjoying DOM Extensions. Using outdated browsers like IE6 and Safari 2 to make your points do not help. I have never experienced method collisions due to DOM Extensions (save for the minor Element#getElementsByClassName issue way back when), so that argument comes across as both contrived and academic. Maybe lots of people have experienced collisions, I don’t know, but I personally have not seen it in the real world.

    My goal was to demonstrate that seemingly elegant and useful idea of extending DOM objects is actually a disaster on many levels. It’s understandable that you “enjoy DOM extensions” — they are certainly very tempting. But what matters (in my opinion) is overall quality of codebase, efficiency of code and future compatibility/maintenance. If NOT extending host objects, positively affects all of those things (as it does), I’ll go with that.

  30. Gravatar

    Don Hopkins said on Jun 17, 2010 @ 7:01

    DOM extensions break JavaScript’s for loop. Therefore they are an extremely bad design decision. End of story. If you think it’s OK to break JavaScript’s for loop, then yes you are a dirty little philistine, and you’re correct to feel that way.

  31. Gravatar

    justin said on Jun 17, 2010 @ 13:57

    @kangax, I’m going to concede your point on this one. In thinking about his more, I see that my primary issue here is calling it a “mistake”. I call it experience. There are those who say “learn from your past mistakes” and then there are those who say “learn from your past experiences”.

    @don, I have never heard of nor ran into an issue with the for loop being broken in my Prototype-dependent applications. Maybe in theory it scares people but in practice has it really been an issue? Sorry, I just really get tired of the academic arguments sometimes.

    And hey, I never said anything about being little, just dirty.

  32. Gravatar

    David Rivers said on Aug 10, 2010 @ 6:54

    You make a thoroughly convincing point to avoid DOM extensions! When will Prototype 2.0 be released?!!

  33. Gravatar

    John-David Dalton said on Oct 14, 2010 @ 10:28

    To throw a few more examples onto the pile:

    Recently solutions like CSS3 PIE, which give IE6-8 CSS3-Fu through CSS behaviors, have been known to crash IE8 when combined with Prototype because Prototype extends the Element.prototype.

    Also Element.addMethods() has issues in Firefox and WebKit because many elements share the HTMLSpanElement constructor.

  34. Gravatar

    Mert said on Jan 19, 2011 @ 18:32

    voloko said:
    #
    First, thanks for the great post.
    I feel that is is generally (not only in this post) considered that extending prototypes is BAD. Though I think

    ….you end up creating a wrapper here for the same element over and over again.

    —————————————————————————————————————-
    You have two assets to worry about when you write your code: How much you fill the heap, and how long the execution takes when you invoke an object’ method.

    In your example, there is nothing to worry about the heap, since local scoping rules apply to a wrapper variable you declare and initialize within a methods body.

    The performance degrade you would get, in your example scenario, that is by invoking wrapper’s method which simply delegates the call to the desired method of the (possibly of some class that descends from Element) object is minuscule.

    Someone above mentioned applying singleton pattern via creating a global variable, which would further improve performance.

    The Dom is based on interfaces, Java script, praised for being extremely object oriented is a language that has no interfaces. Should that be the case, We could write non breaking code through composition;
    a brand new (Non singleton) class say MyClass that Implements interface IElement, that would also have some member of type ElementImpl. Any object of type MyClass would then by contract provide methods with inputs and outputs of expected type. I guess at this point we also get disappointed that function overloading is only possible by changing number of arguments, where we would actually need overloading by argument type.

    One serious problem about any dom element is that attributes are exposed as public properties. They should be only available via accessors, imagine if you would ever run into Element.hide /Element.hide() problem with proper naming conventions applied (i. Element.getIsHidden(), elemnt.hide())

  35. Gravatar

    Teylor Feliz said on Mar 30, 2011 @ 23:10

    Great writeup kangax. Even in controlled environment extending built-in object and host objects is the wrong way to go because you don’t know if that specific browser will be updated in the future and break your code. I also think that it could be a good practice only extending built-in objects (no host objects) with methods that are in the specification of JavaScript but the browser does not supported yet. For example, the trim method in String or the map method in Array.

    Thanks for sharing your knowledge for free! :)

  36. Gravatar

    Smeagol said on Feb 19, 2012 @ 5:08

    http://www.quxing.info/2012/02/19/325/

    here is a chinese version.

  37. Gravatar

    EvilWalrus said on Mar 25, 2012 @ 2:00

    This was a great article with very valid points, and I’ve read through every comment posted here on it too. Word for word – but I still have to disagree, quoting justin “The bigger beef I have with this article is the tone. ” … I strongly feel the real problem was overlooked, and the concept of ‘extension of the DOM’ was incorrectly labeled as the scape goat.

    I think there is still alot of good use out of ‘extending’ DOM elements. I’ve worked in research and experimental development for almost two decades now, the last ten years focusing on client-server bridging. We have developed experiments that have resulted both great failures and success – the failures being the most valuable of experiments ( in the context of education, and progression ) as they often lead to a change in pattern, and eventually a ‘success’

    One of the great successes we had worked on was with the prototype library – extending native (DOM, script, or otherwise) objects with proprietary methods can create a more efficient and cleaner codebase – however, the implicit extension of everything did in fact create a performance issue.

    If you look at the problem as a whole, its not with the ‘extension of the DOM’, its with the objects that gets extended, with what, and when. Rather than throwing out ‘extensions’ completely, why not look at taking more control of what objects you wish to extend, and what you extend them with.

    Implementation of a more restricted, and specific ‘prototyping’ library that conditionally extends native objects with methods (proprietary, or api-based helpers like prototypes ) can not only reduces the codebase, and memory overhead – but also grants the developers an opportunity to think about what they are doing more – and how each line of code effects the application as a whole. Employ the concept of inline-delegation at the domlevel, using rule sets that you dictate based on your applications requirements – when its required ( ie: a lazy evaluation design pattern applied as a dom extension )

    I’ve seen alot of success with this pattern – the key point being, dont rule out DOM extension, the problem isn’t extension of the DOM, its the implementation of the extension – which is entirely in your control.

  38. Gravatar

    william malo said on May 31, 2012 @ 15:59

    Well… Extending the dom is dangerous, but it is in no way dumb or evil.

  39. Gravatar

    Joe said on Sep 7, 2012 @ 10:16

    Aloha!
    I’m relatively new to JavaScript programming, having picked it up maybe 5 years ago, and only getting deep into it a couple years ago. Never looked into library files or was otherwise educated other than O’Reilly’s “JavaScript: The Definitive Guide”. That book made it clear enough to me that, sure you -could- extend native objects via .prototype, that doing so was a bad idea, especially extending the Object constructor (did I see that Prototype.js does that? that’s INSANE).
    But it occurred to me a few months ago that I might extend the Element constructor, not even knowing if it existed. I’m working on a project that is specific to modern browsers, is a more-or-less personal project, so I figured, hey, let’s take a look. I was thrilled to be able to extend DOM Elements via .prototype. Specifically I was craving a “getElementsByClassName” method on every DOM Element. Some browsers I knew already supported this, while others still lag behind the times.
    So I figured the best was to TEST FOR THE EXISTENCE of this method before adding it via prototype. And I created several useful methods:

    // if deep=false (this is the default value for deep)
    // returns a single HTML element
    // or
    // returns false if no matches found
    // if deep=true
    // returns an array of HTML elements (though there may be zero array members)
    if (typeof Element.getAncestor !== 'function')
    Element.prototype.getAncestor=function(cb, deep) {
    if (typeof cb !== 'function') return;
    if (typeof deep === 'undefined') deep=false;
    return (function(parent) { var found=new Array(), grandparent;
    if (cb(parent)) {
    if (!deep) return parent;
    found.push(parent); }
    if (parent.parentNode && (grandparent=arguments.callee(parent.parentNode)))
    found=found.concat(grandparent);
    return deep ? found : ((found.length>0) ? found[0] : false);
    })(this.parentNode); //here we execute the anonymous closure function
    }

    // returns false if no matches found
    if (typeof Element.getElements !== 'function')
    Element.prototype.getElements=function(cb, deep) {
    if (typeof cb !== 'function') return;
    if (typeof deep === 'undefined') deep=true;
    return (function(kids) { var found=new Array(), grandkids, i;
    for (i=0; i<kids.length; i++) {
    if (cb(kids[i])) found.push(kids[i]);
    if (deep && kids[i].hasChildNodes() && (grandkids=arguments.callee(kids[i].childNodes)))
    found=found.concat(grandkids); }
    return found;
    })(this.childNodes); } //here we execute the anonymous closure function and thus return its value

    // returns an array of HTML elements (though there may only be one array member)
    // returns false if no matches found
    if (typeof Element.getElementsByName !== 'function')
    Element.prototype.getElementsByName=function(c, deep) {
    if (typeof c !== 'object' || !(c instanceof RegExp)) c=new RegExp('^'+c+'$');
    var found=this.getElements(function(e) {return e.name && e.name.match( c )}, deep)
    return found.length ? found : false; }

    // returns an array of HTML elements (though there may only be one array member)
    // returns an empty array if no matches found
    if (typeof Element.getElementsByClassName !== 'function')
    Element.prototype.getElementsByClassName=function(c, deep) {
    if (typeof c !== 'object' || !(c instanceof RegExp)) c=new RegExp('\\b'+c+'\\b');
    return this.getElements(function(e) {return e.className && e.className.match( c )}, deep) }

    // getElementsByClassName() and getAncestorByClassName() each work a little different depending on the value of deep
    // if deep=true then an array of all elements found to match (children or ansestors) will be returned.
    // with getElements(), if deep=false then only immediate children will be considered.
    // with getAncestor(), if deep=false then only the closest matching ansestor will be returned (if any).

    // returns a single HTML element if deep=false (this is the default value for deep)
    // returns an array of HTML elements (though there may only be one array member) if deep=true
    // returns false if no matches found
    if (typeof Element.getAncestorByClassName !== 'function')
    Element.prototype.getAncestorByClassName=function(c, deep) {
    if (typeof c !== 'object' || !(c instanceof RegExp)) c=new RegExp('\\b'+c+'\\b', 'i');
    return this.getAncestor(function(p) {return (p.className && p.className.match( c ))}, deep) }

    // returns (e) if the element (e) is an ancestor of the Element.
    // else returns false.
    if (typeof Element.hasAncestor !== 'function')
    Element.prototype.hasAncestor=function(e) {return this.getAncestor(function(a) {return (a===e)})}

    if (typeof Element.addClassName !== 'function')
    Element.prototype.addClassName=function( c ) { // c must be the string name of the class
    if (!(this.className && this.className.match( new RegExp('\\b'+c+'\\b', 'i') )))
    this.className+=(this.className) ? (" "+c) : c; }

    // addClassName() will not add the CSS class name if it already exists as such.
    // removeClassName() will remove all copies of the CSS class name,
    // and also stray & multiple spaces between CSS class names, found in Elment.className.
    // both allow and respect an Elment.className to contain multiple CSS class names.

    if (typeof Element.removeClassName !== 'function')
    Element.prototype.removeClassName=function( c ) { // c may be the string name of the class or a RegExp
    if (typeof c !== 'object' || !(c instanceof RegExp)) c=new RegExp('\\b'+c+'\\b', 'ig');
    this.className=this.className.replace(c, "");
    this.className=this.className.replace( /^\s*/ , "");
    this.className=this.className.replace( /\s*$/ , "");
    this.className=this.className.replace( /\s{2,}/ , " "); }

    if (typeof Element.useClassName !== 'function')
    Element.prototype.useClassName=function(c, b) { // c should be the string name of the class
    if (b) this.addClassName( c );
    else this.removeClassName( c ); }

    Seems to me this is perfectly acceptable in most modern environments (it’s 2012 now and I’ve really given up on MSIE < 8 for the most part). Why? Because the method names are pretty explicit and don't conflict with what even the above or below average web site are likely to use for Element attributes or <input> names.
    However, many of the DOM extensions that I see here that Prototype.js implenets I see as bogus, and perhaps even overkill. Do you really need a .hide() method? Come on! .style.display='none' is very simple, clear, and strait-forward. I use a global function I call disabler() that handles more complicated situations, which is much more flexible in application than a DOM extension.
    My point is that, while I haven't looked at prototype.js beyound what I see in this web page, it is overkill of DOM- and native-object extension that is the problem.
    I grew up programming in Assembly Language in the early 1980s on systems that required that language to do anything in real-time. I learned quickly the importance of not only well-commenting your code, but of writing "elegant" code. JavaScript is an object-oriented language, and embracing that style in a consistent manner is a step toward elegance.
    Consider:

    //elegant...
    document.getElementById('myTable').getElementsByClassName('evenRows');

    //not so elegant...
    $(document.getElementById('myTable')).getElementsByClassName('evenRows');
    getElementsByClassName(document.getElementById('myTable'), 'evenRows');

    What happens when it gets much more complicated than that? By extending the DOM your code reads simply from left to right, but with either of the other solutions, you must keep track of parenthesis, sometimes at multiple levels.
    Other arguments don’t hold much water for me like “when another programmer ready your code, they wonder ‘where that came from’”. So they were slack and didn’t read the top of the file, or pay attention that the DOM extensions were loaded into the page in a previous file (always load these types of files in the document head, so they don’t get lost in the document). This is all the responsibility of the web developer, not the library developer. Just because the web developer is slack, don’t blame the guy building the tools. And with method names like these, the average developer with half a brain-cell left (after global pollution has killed the rest off) can tell what they do, and can infer that, yes, a prototype library was included somewhere.
    And so what about non-native properties showing up in for (prop in Element)? Have you tried this statement? There are SO MANY properties there that to select a property or set of properties by exclusion is near-impossible. Contrast this with Array.prototype.map which can easily muck-up other code-bases that relay on custom properties attached to arrays. Been temped by that for years, but I only attach that method to individual arrays on an as-needed basis.
    So native methods return Node-lists, while mine returns an array. And the problem is??? The problem would be slack programmers that don’t understand the difference when they write the code. So that is the problem is every language in every environment. The way society is going, it will be law that we need helmets and elbow/knee pads to walk to the mailbox at the end of the driveway, lest we might fall and hurt ourselves.
    One good point, though, is that if browsers DO implement these methods natively in the future, there is no telling what arguments they will accept, or values will be returned. I find returning false instead of an empty array is more useful, but I had to modify my original “getElementsByClassName” code once I tested it on all browsers. This fact I see as the only limiting factor to carefully applied DOM Element .prototype extensions. But even that can be weighed in as a reasonable, finite value, as I predict that most of these methods will not be natively implemented within the lifespan of the average web-page. And as so many web-pages are being developed using automated processes these days in which the developer doesn’t even know how to code, the old page is discarded when updating the site anyway, and at that time the state of the DOM can be re-assessed.
    Peace to all—

  40. Gravatar

    RobG said on Sep 13, 2012 @ 22:30

    I think the statement “That same specification also defines inheritance between those interfaces” isn’t quite correct. The specification essentially says one interface has all the methods and properties of another interface plus some extra stuff. It doesn’t say that must be based on some kind of inheritance, though that is a reasonable way to do it. The specifications are also language neutral, so inheritance doesn’t have to be prototypal either (though of course in a host supporting javascript it makes sense to implement interfaces using prototype inheritance).

    There are also good reasons not to do it, such as to deliberately prevent programmers from messing with host object inheritance, which is not a good idea even where it is possible.

  41. Gravatar

    Serwis LaptopóW WrocłAw said on Oct 19, 2012 @ 18:30

    I’d like to find out more? I’d love tо find οut
    some аdditional informаtіon.

  42. Gravatar

    Marc K said on Feb 17, 2013 @ 11:47

    I agree but only from a perspective of the DOM standard, browser implementations and mixing libraries. From the perspective of the Javascript, being a prototyping language, there is nothing technically wrong with doing this. Extending prototypes is exactly what Javascript programmers should be doing and that is what everyone has taught you to do for ages.

    There are ways to avoid the issues browser developers should have done in the very beginning. An easy example would be to add their own prefix to non-standard properties and methods, exactly with what they are doing with CSS. Aged programmers have known this since long time ago.

    Why programmers insist on doing el.myFoo instead of el.foo is beyond me.

Trackbacks

  1. Ajaxian » Prototype 2.0 will not extend the DOM said:

    [...] kindly goes into graphic detail on why it is heinous, and how Prototype 2.0 will not follow this [...]

  2. Prototype 2.0 will not extend the DOM - Programming Blog said:

    [...] above quote comes from the infamous Kangax (of Prototype Core). He kindly goes into graphic detail on why it is heinous, and how Prototype 2.0 will not follow this [...]

  3. Cheatsheet: 2010 04.01 ~ 04.07 - gOODiDEA.NET said:

    [...] What’s wrong with extending the DOM [...]

  4. Are Prototype 2.0 will not extend the DOM? | Intipadi.com said:

    [...] kindly goes into graphic detail on why it is heinous, and how Prototype 2.0 will not follow this [...]

  5. links for 2010-04-14 « Breyten’s Dev Blog said:

    [...] Perfection kills » What’s wrong with extending the DOM (tags: javascript prototype dom arguments explanation) [...]

  6. Library foundation code – James Padolsey said:

    [...] universally accepted that extending the DOM directly (via Element.prototype, Node.prototype etc.) is a bad idea. To combat the inherent problems with doing this, quite a few libraries (jQuery, BBC Glow, etc.) [...]

  7. Library foundation code | DesignerLinks | Home to Web design news, jQuery Tutorials, CSS tutorials, Web Designing tutorials, JavaScript tutorials and more! said:

    [...] universally accepted that extending the DOM directly (via Element.prototype, Node.prototype etc.) is a bad idea. To combat the inherent problems with doing this, quite a few libraries (jQuery, BBC Glow, etc.) [...]

  8. Internet Explorer Event Handler Leaks – Reign Drops Fall... said:

    [...] “host objects” that don’t have to follow the rules that natvie objects do (see What’s Wrong With Extending the DOM by Juriy Zaytsev for a run-down of host object wackiness). Circular references between host objects [...]

  9. RegexHacks :: Blog » The Top 150 Web Development Highlights from 2010 said:

    [...] 74. What’s Wrong with Extending the DOM [...]

  10. The Top 150 Web Development Highlights from 2010 | Programming Blog said:

    [...] 74. What’s Wrong with Extending the DOM [...]

  11. JavaScript: What are the reasons that make jQuery more popular than MooTools? - Quora said:

    [...] that still thinks extending the prototype of built-in JavaScript objects is a good idea.See also:http://perfectionkills.com/whats…http://www.nczonline.net/blog/20…1 Comment • Insert a dynamic date here  Armin [...]

  12. Why most JS devs don’t understand OOP | Blog | Miller Medeiros said:

    [...] you shouldn’t extend DOM elements, it’s considered a bad practice. Comments [...]

  13. Perfection kills » Extending built-in native objects. Evil or not? said:

    [...] and extending host objects. I tried to explain what’s wrong with extending host objects in a blog post, a while back. Now, if you look at the list of problems with extending host objects it’s easy to see that [...]

  14. 扩展原生对象与 es5-safe 模块 « 岁月如歌 said:

    [...] Host Object 是邪恶的,很难实现,即便实现了也会有很多隐患,具体请参阅:What’s wrong with extending the DOM. 可以说对 DOM 对象进行扩展的决定就注定了 Prototype.js 的没落(DOM extension is [...]

  15. Boom.js Release said:

    [...] mootools是通过扩展HTMLElement.prototype 来扩展DOM API ,在不支持此方法的ie6下把所有方法直接赋值给DOM对象,当页面里充斥大量用这种方法扩展来的DOM对象后,会带来性能灾难。而且扩展HTMLElement的原型对象本身并不是好方法(Whats Wrong With Extending The Dom)。 [...]

  16. Why is extending built-in javascript types evil? - Quora said:

    [...] is a pretty comprehensive article about the pitfalls of using prototype extensions in the DOM: http://perfectionkills.com/whats…I've brought up the negative points regarding why prototype extension might be harmful but keep [...]

  17. Extending JavaScript Natives « JavaScript, JavaScript… said:

    [...] more on the perils of extending DOM objects see this fine article by [...]

  18. On webkit-only mobile javascript libraries said:

    [...] functions like addClass or removeClass to an element, or ready to a document. That way they avoid extending the DOM or implementing a wrapper around an HTMLElement like jQuery does with its jQuery object, but only [...]

  19. Is Prototype 2.0 still being built? - Quora said:

    [...] to Add and Search Questions; Search Topics and People Is Prototype 2.0 still being built?http://perfectionkills.com/whats…RepostCannot add comment if you are logged out.  Add [...]

  20. What’s wrong with extending the DOM – Perfection Kills | phato.blog` said:

    [...] What’s wrong with extending the DOM อธิบายได้อย่างดี และยาว [...]

  21. My Stream | The Top 150 Web Development Highlights from 2010 | My Stream said:

    [...] 74. What’s Wrong with Extending the DOM [...]

  22. Smeagol's blog » Blog Archive » 扩展DOM有什么问题(译文) said:

    [...] 原文地址:what’s wrong with extending the DOM [...]

  23. 分享一下浏览器里保存下来的书签 | w3ctech said:

    [...] Perfection kills » What’s wrong with extending the DOM [...]

  24. adding an object or a function to a html dom element (specifically canvas) | PHP Developer Resource said:

    [...] would need to add your methods to HTMLCanvasElement.prototype. Yet, there is a good article about What’s wrong with extending the DOM. Read it and use another solution, e.g. the proposed object wrappers. If you use jQuery or some lib [...]

  25. Extending native prototypes is good (sometimes) « typeof.it said:

    [...] PrototypeJS: The first of many, its main flaw is that it extends the DOM. Which is a bad idea. [...]

  26. Extending built-in native objects. evil or not? | JSSpy | The ultimate resource for Javascript info. said:

    [...] and extending host objects. I tried to explain what’s wrong with extending host objects in a blog post, a while back. Now, if you look at the list of problems with extending host objects it’s easy to see that [...]

  27. Rounded Corners 353 — Security questions | Labnotes said:

    [...] What’s wrong with extending the DOM. Lessons learned from the Prototype.js experiment. TL;DR don’t extend host [...]

  28. 柳明的博客 - 深圳博客在线,致力于打造一个高效的互联网技术交流平台 said:

    [...] 扩展DOM有什么问题 作者:liuming  发表于:2012年10月19日 23:30  分类:javascript, web前端  阅读:1次  原文地址:what’s wrong with extending the DOM [...]

  29. 扩展DOM有什么问题 | 柳明的博客 said:

    [...] 原文地址:what’s wrong with extending the DOM [...]

  30. Extending the DOM video said:

    [...] recently read that extending the DOM on the whole is bad, because of reasons listed here and here. As I understand it, the main reasons against it [...]

  31. Intelytics said:

    [...] we inherit from JavaScript current objects such as Array or even DOM elements [...]

  32. Using object wrappers to extend the JavaScripts DOM? - How-To Video said:

    [...] = function(className) { this.className += ' ' + className; }; However after much reading (perfectionkills.com/whats-wron… was good) it seems this is a terrible way to extend the DOM for a number of reasons.The above [...]

Leave a Comment

Please, don't forget to escape your input (<, > and &). Wrap code sections with <pre>

Allowed tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>