11

I want to write some Javascript classes which extend DOM nodes (so that I can then insert instances of my class directly into the DOM), but am having difficulty finding out which class/prototype I should inherit from.

E.g.:

function myExtendedElement() {
       this.superclass = ClassA;
       this.superclass();
       delete this.superclass;
} 

But what should ClassA be?

kangax
  • 38,898
  • 13
  • 99
  • 135
wheresrhys
  • 22,558
  • 19
  • 94
  • 162

5 Answers5

22

It's not a good idea to do this.

First of all, to inherit from DOM element, you need to have access to that element's prototype. The problem is that not all browsers provide access to prototypes of DOM elements. Newer Gecko and WebKit -based clients, for example, expose some of these prototypes as global objects - HTMLDivElement, HTMLElement, Element, Node, etc.

For example, plain DIV element usually has a prototype chain similar to:

HTMLDivElement.prototype -> HTMLElement.prototype -> Element.prototype 
  -> Node.prototype -> Object.prototype -> null

You can access any of them and extend or inherit from as desired. But again, even though you can, I strongly advise not to.

When browser doesn't expose these prototypes, you're pretty much out of luck. You can try retrieving them by following constructor property of DOM element itself -

document.createElement('div').constructor;

- but then there's no guarantee that element has constructor property (e.g. IE6 doesn't) and even if it does, that this property references "correct" object. If, after all, constructor does reference correct object, there's still no guarantee that this objects is allowed to be augmented at all. The truth is that host objects are allowed to implement completely bizarre behavior and do not even have to follow rules that native JS objects follow (you can find dozens of such examples in real life).

Second reason you want to avoid inheriting from DOM element prototypes is that mechanism of such inheritance is not really specified anywhere; it could be quirky, unpredictable and overall fragile and unreliable.

Yes, you can create a constructor that would initialize objects with proper prototype chain (i.e. having DOM prototype in it):

function MyDivElement(){}
MyDivElement.prototype = HTMLDivElement.prototype;

var myDiv = new MyDivElement();
typeof myDiv.appendChild; // "function"

- but this is as much as it goes, and usefulness of this whole approach becomes limited by having certain methods in prototype and nothing else -

typeof myDivElement.nodeName; // "undefined"
myDivElement.innerHTML = '<span>foo<\/span>';
myDivElement.childNodes; // Error

Until some standard specifies exact mechanism for inheriting from DOM prototypes (and browsers actually implement that mechanism), it's best to leave them alone, and perhaps try alternative approach - e.g. wrapper or decorator patterns rather than prototype one :)

kangax
  • 38,898
  • 13
  • 99
  • 135
  • 1
    You might also be interested in http://perfectionkills.com/whats-wrong-with-extending-the-dom/ – kangax Jul 22 '10 at 13:04
  • >5 years later, maybe this is outdated? – chris Feb 03 '15 at 19:46
  • @helloChris http://perfectionkills.com/whats-wrong-with-extending-the-dom/#comment-1195576906 – kangax Feb 04 '15 at 12:13
  • 3
    This is outdated. You can extend the DOM with custom elements, which are required to inherit from HTMLElement; work has been ongoing since at least 2013 https://www.w3.org/TR/custom-elements/ – morewry Apr 13 '17 at 20:07
  • 1
    LOLz _work has been ongoing since at least 2013_ and yet it is 2017 and [`registerElement` on caniuse.com](http://caniuse.com/#search=registerElement) is still pretty much red across the board! – Sukima Aug 07 '17 at 17:13
3

Old Q but there's a better answer than "Do" or "Don't" now that IE6 is mostly defunct. First of all prototyping core ECMA endpoint-inheritance constructors like 'Array' is pretty harmless and useful if you do it properly and test to avoid breaking existing methods. Definitely stay away from Object and think real hard before messing with Function, however.

If you're sharing code between a lot of people/authors, or dealing with DOM uncertainty, however, it's typically better to create adapter/wrapper objects with a new factory method to use in an inheritance-scheme.

In this case I wrote document.createExtEl to create wrapped DOM elements whose accessible properties are all available via prototype.

Using the following, your "superclass" for divs would be HTMLExtDivElement (in this case globally available - ew, but it's just an example). All references to the original HTMLElement instance's available properties live inside the wrapper's prototype. Note: some old IE properties can't be passed as references or even accessed without throwing errors (awesome), which is what the try/catch is for.

You could normalize common properties by adding logic to put missing or standardized properties in right after the loop wraps instance-available properties but I'll leave that to you.

Now for the love of Pete, don't ever use my code to write some cascading 16-times inheritance foolishness and then implement in some ironically popular library we're all forced to deal with or I will hunt you down and loudly quote "Design Patterns" at you while throwing rotten fruit.

//Implementation just like document.createElement()
//document.createExtEl('div').tagName === 'DIV'

document.createExtEl = ( function(){  //returns a function below

            var htmlTags = ['div','a'], //... add all the element tags you care to make extendable here
            constructorMap = {},
            i = htmlTags.length;

            while(i--){
                thisTag = htmlTags[i].toLowerCase();
                constructorMap[ thisTag ] = function(){
                    var elObj = document.createElement(thisTag),
                    thisProto = this.constructor.prototype,
                    constructorName = 'HTMLExt' + thisTag.charAt(0).toUpperCase() + thisTag.slice(1) + 'Element';

                    alert(constructorName);

                    window[constructorName] = this.constructor; //adds a global reference you can access the new constructor from.

                    for(var x in elObj){ try{ thisProto[x] = elObj[x]; } catch(e){} }
                }
            }

            //all of the above  executes once and returned function accesses via closure
            return function(tagName){
                return new constructorMap[tagName.toLowerCase()]();
            }



    } )()

    //Now in the case of a superclass/constructor for div, you could use HTMLExtDivElement globally
Erik Reppen
  • 4,605
  • 1
  • 22
  • 26
2

In 2020, you can easily create a custom element by extending HTML elements.

class AppDrawer extends HTMLElement {...}
window.customElements.define('app-drawer', AppDrawer);

// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer', class extends HTMLElement {...});

More info and reference: Custom Elements

Reza
  • 3,473
  • 4
  • 35
  • 54
1

You can simply add new functions to the DOM prototypes, eg.

Element.prototype.myNameSpaceSomeFunction = function(...){...}

Then myNameSpaceSomeFunction will exist on all elements.

olliej
  • 35,755
  • 9
  • 58
  • 55
0

I've found a hack that works... at least, it enables me to access the extended object properties via the DOM element and vice versa. But it's hardly elegant.

var DOMelement = document.getElementById('myID'); // or  $('#myID')[0]; in jQuery
DOMelement.extended = new extensionClass(this);

function extensionClass(element) {
    this.element = element;
    ...
}
wheresrhys
  • 22,558
  • 19
  • 94
  • 162
  • 1
    You should actually **avoid adding properties to elements** like that (also known as expandos). Not only is it not safe to augment host objects, chances are you'll accidentally create circular reference between host object and native object, which **leaks** in some versions (<8, IIRC) of IE. – kangax Sep 29 '09 at 12:48
  • I am wary of creating circular references and will be testing my app with some ridiculously big instances of it to see if any effect is noticeable. Why is it otherwise not safe to augment host objects? – wheresrhys Sep 30 '09 at 20:10