views:

406

answers:

2

I'd love it if I could make my own class that extended an HTML element class, e.g. HTMLDivElement or HTMLImageElement.

Assuming the standard inheritance pattern:

// class A:

function ClassA(foo) {
    this.foo = foo;
}

ClassA.prototype.toString = function() {
    return "My foo is " + this.foo;
};

// class B:

function ClassB(foo, bar) {
    ClassA.call(this, foo);
    this.bar = bar;
}

ClassB.prototype = new ClassA();

ClassB.prototype.toString = function() {
    return "My foo/bar is " + this.foo + "/" + this.bar;
};

I'm not able to figure out what to call in the constructor:

function Image2() {
    // this doesn't work:
    Image2.prototype.constructor.call(this);
    // neither does this (only applies to <img>, no equivalent for <span> etc.):
    Image.call(this);
}

Image2.prototype = new Image(); // or document.createElement("img");

Image2.prototype.toString = function() {
    return "My src is " + this.src;
};

Is this just not possible? Or is there some way? Thanks. =)

+1  A: 

This might help you. In this Javascript based game, he extended table elements in the DOM. MiniRacer on CodeProject.

And I believe he got his technique from this article by Paul Sowden.

Josh Stodola
Thanks, but unfortunately this isn't truly creating a subclass; it's just extending instances in disguise. The biggest drawbacks to me are that (x instanceof Image2) won't return true and you can't use the constructor function (new Image2). More subjective is that it's also modifying a built-in function, which is hacky, but I won't argue that too much!
Aseem Kishore
+1  A: 

I'm not able to figure out what to call in the constructor

Image2.prototype.constructor.call(this);

Yeah, JavaScript's objects don't have classes and chainable constructors like that. It has its own model of prototypes, which can be quite confusing, but doesn't actually deliver the proper potential of prototype inheritance because it's still tied to constructor-functions, which look like class constructors but aren't really.

unfortunately this isn't truly creating a subclass; it's just extending instances in disguise

Yes - that's all JavaScript gives you. If you want to, you can certainly create your own object system that abstracts this away so it seems like you're creating classes and instances. I've attached a flavour I often use at the bottom as an example. But there is no standard for creating a subclass of anything in JavaScript.

For example, in your "standard" code you say:

ClassA.call(this, foo);

but this only works because you have written ClassA in such a way that you can get away with calling it twice, once for the prototype ClassA, with 'foo' set to 'undefined', and then again on the object that inherits from that prototype, to mask that 'foo' behind another 'foo' over the top.

This is not anything actually standard that classes might be expected to do for each other, and it's certainly not specified that this will work for non-JS-native browser objects:

function MyImage() {
    Image.call(this); // might do anything. will almost certainly fail
}
MyImage.prototype= new Image();

In any case, setting a prototype to something like an HTMLElement that isn't a native-JavaScript object is not specified by the ECMAScript standard and has varying levels of success; in IE it doesn't work at all, the prototype's properties don't take.

Also,

Image2.prototype.constructor

from your example may actually not be the same as 'Image' as you would expect. 'constructor', apart from being a non-standard property that's not available in all browsers, does something that in a class system would be Not What You Think at all; it is best avoided.

Image2 should have all properties/methods of Image plus more

Currently the only way to do this is with a wrapper of some sort. You can't do this with prototypes for the reasons above; you'd have to add the properties to every instance separately:

function makeImage2() {
    var image= Image();
    image.isBig= function() {
        return this.width>200 || this.height>200;
    };
}

this is just a factory function of course, and not anything like subclassing. You would have to provide your own replacement for instanceof.

...

// An example class system
//
Function.prototype.subclass= function() {
    var c= new Function(
        'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+
        'if (arguments[0]!==Function.prototype.subclass._FLAG && this._init) this._init.apply(this, arguments); '
    );
    if (this!==Object)
        c.prototype= new this(Function.prototype.subclass._FLAG);
    return c;
};
Function.prototype.subclass._FLAG= {};

// Usage
//
ClassA= Object.subclass();
ClassA.prototype._init= function(foo) {
    this.foo= foo;
};
ClassA.prototype.toString= function() {
    return 'My foo is '+this.foo;
};

ClassB= ClassA.subclass();
ClassB.prototype._init= function(foo, bar) {
    ClassA.prototype._init.call(this, foo);
    this.bar= bar;
};
ClassB.prototype.toString= function() {
    return ClassA.prototype.toString.call(this)+' and my bar is '+this.bar;
};
bobince