views:

328

answers:

3

For a long time I have been throwing around the idea of making my JavaScript more object oriented. I have looked at a few different implementations of this as well but I just cannot decide if it is necessary or not.

What I am trying to answer are the following questions

  • Is John Resig's simple inheritance structure safe to use for production?
  • Is there any way to be able to tell how well it has been tested?
  • Besides Joose what other choices do I have for this purpose? I need one that is easy to use, fast, and robust. It also needs to be compatible with jQuery.
+13  A: 

Huh. It looks much more complicated than it needs to be, to me.

Actually looking more closely I really take exception to what it is doing with providing this._super() whilst in a method, to call the superclass method.

The code introduces a reliance on typeof==='function' (unreliable for some objects), Function#toString (argh, function decomposition is also unreliable), and deciding whether to wrap based on whether you've used the sequence of bytes _super in the function body (even if you've only used it in a string. and if you try eg. this['_'+'super'] it'll fail).

And if you're storing properties on your function objects (eg MyClass.myFunction.SOME_PRIVATE_CONSTANT, which you might do to keep namespaces clean) the wrapping will stop you from getting at those properties. And if an exception is thrown in a method and caught in another method of the same object, _super will end up pointing at the wrong thing.

All this is just to make calling your superclass's method-of-the-same name easier. But I don't think that's especially hard to do in JS anyway. It's too clever for its own good, and in the process making the whole less reliable. (Oh, and arguments.callee isn't valid in Strict Mode, though that's not really his fault since that occurred after he posted it.)

Here's what I'm using for classes at the moment. I don't claim that this is the “best” JS class system, because there are loads of different ways of doing it and a bunch of different features you might want to add or not add. But it's very lightweight and aims at being ‘JavaScriptic’, if that's a word. (It isn't.)

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw 'Constructor function requires new operator';
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    if (this!==Object) {
        Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
        Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    }
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

It provides:

  1. protection against accidental missing new. The alternative is to silently redirect X() to new X() so missing new works. It's a toss-up which is best; I went for explicit error so that one doesn't get used to writing without new and causing problems on other objects not defined like that. Either way is better than the unacceptable JS default of letting this. properties fall onto window and mysteriously going wrong later.

  2. an inheritable _init method, so you don't have to write a constructor-function that does nothing but call the superclass constructor function.

and that's really all.

Here's how you might use it to implement Resig's example:

var Person= Object.makeSubclass();
Person.prototype._init= function(isDancing) {
    this.dancing= isDancing;
};
Person.prototype.dance= function() {
    return this.dancing;
};

var Ninja = Person.makeSubclass();
Ninja.prototype._init= function() {
    Person.prototype._init.call(this, false);
};
Ninja.prototype.swingSword= function() {
    return true;
};

var p= new Person(true);
p.dance(); // => true

var n = new Ninja();
n.dance(); // => false
n.swingSword(); // => true

// Should all be true
p instanceof Person &&
n instanceof Ninja && n instanceof Person

Superclass-calling is done by specifically naming the method you want and calling it, a bit like in Python. You could add a _super member to the constructor function if you wanted to avoid naming Person again (so you'd say Ninja._super.prototype._init.call, or perhaps Ninja._base._init.call).

bobince
`typeof` is only unreliable for functions if you're dealing with host objects, and you wouldn't want to subclass one of those. Or at least, you shouldn't want to. I certainly agree that Resig's code is overly tricksy and fragile, and there's simply no need for it.
Tim Down
@Tim: Yeah, you can't prototype off a host object, but what the function test is doing is checking each of the members, rather than the superclass object. So a host object being a member of your class can cause problems. (eg in Firefox 3.0 a regexp comes out as function, so the wrapping in Class.extend could nadger regexp members by trying to wrap them like functions).
bobince
Oh, I always forget about the regexp thing. And yes, you're right; I hadn't read through Mr Resig's code as thoroughly as I might have done, and now it looks even more fragile than it did before. Good use of the word "nadger", btw. It's underused on SO.
Tim Down
This is a GREAT example bobince.....I just want to wait and see if anyone else has any other suggestions before I accept it. Thanks a lot for being so thorough on it.
Metropolis
+4  A: 

JavaScript is prototype based and not class based. My recommendation is not to fight it and declare subtypes the JS way:

MyDerivedObj.prototype = new MySuperObj();
MyDerivedObj.prototype.constructor = MyDerivedObj;
Christopher Hunt
The problem with this is that calling `new MySuperObj()` invokes the initialisation code in the constructor function for `MySuperObj()`, as if you were actually instantiating a `MySuperObj`. As soon as you have anything non-trivial in the constructor function, this doesn't work, as you need to call the `MySuperObj` initialisation code when you construct a `MyDerivedObj` instance, not at define-time.
bobince
I concur that you might want to factor out your initialisation code in to a separate method and call it from MyDerivedObj's constructor... better still, consider using Dependency Injection. I've found that this cuts down a great deal on constructor code.
Christopher Hunt
+3  A: 

See how far you can get without using inheritance at all. Treat it as a performance hack (to be applied reluctantly where genuinely necessary) rather than a design principle.

In an a highly dynamic language like JS, it is rarely necessary to know whether an object is a Person. You just need to know if it has a firstName property or an eatFood method. You don't often need to know if an object is an array; if it has a length property and some other properties named after integers, that's usually good enough (e.g. the Arguments object). "If it walks like a duck and quacks like a duck, it's a duck."

// give back a duck
return { 
  walk: function() { ... }, 
  quack: function() { ... } 
};

Yes, if you're making very large numbers of small objects, each with dozens of methods, then by all means assign those methods to the prototype to avoid the overhead of creating dozens of slots in every instance. But treat that as a way of reducing memory overhead - a mere optimisation. And do your users a favour by hiding your use of new behind some kind of factory function, so they don't even need to know how the object is created. They just need to know it has method foo or property bar.

(And note that you won't really be modelling classical inheritance in that scenario. It's merely the equivalent of defining a single class to get the efficiency of a shared vtable.)

Daniel Earwicker