DANGER, WILL ROBINSON!
There’s a small example in the Readme for the project, which illustrates the use of the framework:
In this example, I’m using the `define` method to create a new “class” (re: constructor function), using `extend` to inherit from that object and create yet another “class” (re: constructor function), and then use those objects to do some work – including the ability to call the `super` object of the one that I’ve extended in to.
It works fairly well. The few bits of functionality that you see in this case show the proper results. But there’s some pretty serious problems with writing code this way.
Classy Frameworks: A Mistake
From Douglas Crockford:
He also says at the bottom of his Classical Inheritance page:
Frankly, I agree with him. I think there’s some potential value in a framework like this, in specific scenarios. But the uses of it seem to be more and more limited, the more I learn about good prototypal inheritance patterns.
From my Objects & Prototypes screencast:
“More than just the remaining bugs [in the ClassyObjects framework], though, wether or not a class-y framework is a good idea to begin with is a subject of intense debate.
A class-y framework like this is powerful, indeed, and there are times when it can come in handy. Backbone is a good example, again. Jeremy Ashkenas – the creator of Backbone – recognized the need to provide a simple inheritance mechanism for the objects in Backbone so he provided one. But at the same time, he didn’t split the inheritance framework out in to it’s own library. I remember reading a comment at one point where he said he didn’t want to impose that style or it’s limitations on anyone outside of Backbone.
For all of the convenience that we created, the class-y objects framework imposes a lot of overhead and brings it’s own limitations and issues. So I say we should embrace prototypes and prototypal inheritance and relegate the class-y frameworks, like the one we just wrote, to the special cases where the advantages may outweigh the disadvantages.”
Overhead And Other Concerns
Ok, enough of the nebulous rhetoric… there are a few real problems that I see in code like this. Chief among them are:
- Implying a “class” definition, which can be dangerous for inexperienced JS devs
- The overhead of defining the multiple layers of inheritance
- The overhead of managing the “super” context correctly
Implying A Class Definition
Overhead Of Inheritance Layers
Look at it this way: when you have a standard prototypal inheritance chain going on, you have at most the number of objects that you are directly working with:
In this example, there are two objects that we defined and used: MyObject and InheritingObject. InheritingObject’s prototype is MyObject, directly. Any change we make to MyObject will be directly reflected in InheritingObject.
Now look at the the “inherits” function from the ClassyObjects framework, and a very simple usage of it:
In the example usage, it might look like we are only dealing with two objects. But the truth is we are dealing with no less than 5 objects: MyObject, ConstructorFunction, ConstructorFunction.prototype, the “definition” object literal, and finally the object instance that we create from the resulting “class-y” object.
We’ve added 2.5 times the number of layers to our system, so that we can create a class-like structure.
But there are some benefits to this. We’re not adding all of these layers for the sake of adding them. In the prototype example where we only have two objects in use, modifying MyObject will result in changes being available to InheritingObject. This might not be the desired behavior, and the ClassyObjects framework solves that with the additional overhead.
When we call “inherits” to create a new constructor function (“class”), we add the extra “inhertingInstance” object as the prototype of our ConstructorFunction specifically so that we can isolate the prototype of the new objects from the original object we extended. This means we can directly modify the “MyClass.prototype” object and have it affect all of our MyClass instances, while still isolating the original MyObject from those changes.
Overhead Of Managing “this” in “super”
You’re going to end up with an infinite loop and a stack overflow problem. The problem is caused the use of “this.super”. In the call to “bar.baz”, the context of the call is set to the “bar” object. But the “baz” function doesn’t exist on “bar”, so it looks at “foo” for the function. Now the method definition for “foo.baz” calls “this.super.baz()”, which looks like it should call “root.baz”, right? After all, the “super”-class of “foo” is “root”. But since the function execution context has been set to “bar”, “this” still refers to “bar”. Therefore, “this.super.baz” will effectively call “bar.baz” – which is the original call that we made to start this whole thing off, thus resulting in an infinite loop.
You can fix this, though. You can add the overhead of wrapping “super” as a function and then wrapping the “super” function of each object in a bound context function. EmberJS does this, for example, and there’s a tremendous amount of overhead involved again. It’s akin to the way we created additional inheritance layers in order to isolate the prototype of a “class-y” constructor from the object that it extends.
More layers, more overhead, more potential for slowing down your application and your framework. No thanks.
Still, It Has it’s Uses (I Think)
Sure, there are likely ways in which Backbone and Ember could facilitate their inheritance without the use of a class-like infrastructure. I don’t know that it would serve the needs of the end-user as well as a class-like framework, though. But then, I haven’t seen an MV* framework or library that takes this approach, yet. Maybe they are out there – and I’d love to see one. If one doesn’t exist, though, maybe it’s time to write one.
For More Info On Prototypes…