Closing is your friend!
Just add the following tiny function to the top-level namespace and you are ready for OOP, complete with
- encapsulation with static and instances, private and public variables and methods
- inheritance
- class level injection (e.g. for Singleton services)
- no limits, no framework, just old javascript
function clazz(_class, _super) { var _prototype = Object.create((_super || function() {}).prototype); var _deps = Array.isArray(_class) ? _class : [_class]; _class = _deps.pop(); _deps.push(_super); _prototype.constructor = _class.apply(_prototype, _deps) || _prototype.constructor; _prototype.constructor.prototype = _prototype; return _prototype.constructor; }
The above function simply connects the prototype of this class and a possible parent constructor and returns the resulting constructor, ready to instantiate.
Now you can naturally declare your base classes (that is, extend {}) in several lines of code, complete with static, instances, public and private properties and methods:
MyBaseClass = clazz(function(_super) { // class closure, 'this' is the prototype // local variables and functions declared here are private static variables and methods // properties of 'this' declared here are public static variables and methods return function MyBaseClass(arg1, ...) { // or: this.constructor = function(arg1, ...) { // local variables and functions declared here are private instance variables and methods // properties of 'this' declared here are public instance variables and methods }; });
Class extension? All the more natural:
MySubClass = clazz(function(_super) { // class closure, 'this' is the prototype // local variables and functions are private static variables and methods // properties of this are public static variables and methods return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) { // local variables and functions are private instance variables and methods _super.apply(this, arguments); // or _super.call(this, arg1, ...) // properties of 'this' are public instance variables and methods }; }, MyBaseClass); // extend MyBaseClass
In other words, pass the constructor of the parent class to the clazz function and add _super.call(this, arg1, ...) to the constructor of the child class, which calls the constructor of the parent class with the necessary arguments. As with any standard inheritance scheme, the call to the parent constructor must be the first in the child constructor.
Please note that you can explicitly specify the constructor using this.constructor = function(arg1, ...) {...} or this.constructor = function MyBaseClass(arg1, ...) {...} if you you need simple access to the constructor from the code inside the constructor or even just return the constructor with return function MyBaseClass(arg1, ...) {...} as in the above code. Depending on what you like best.
Just create objects from such classes, as usual, from the constructor: myObj = new MyBaseClass();
Note that closures perfectly encapsulate all the functionality of a class, including its prototype and constructor, providing a natural namespace for static and instances, private and public properties, and methods. The code inside the class closure is completely free of restrictions. No framework, no limitations, just old Javascript. Closure rule!
Oh, and if you want to embed single-leaf dependencies (e.g. services) in your class (i.e. prototype), clazz will do this for you on la AngularJS:
DependentClass = clazz([aService, function(_service, _super) {
As you can see from the above code, to inject singleton into a class, just put the closure of the class as the last record in the array with all its dependencies. Also add the appropriate parameters to close the class before the _super parameter and in the same order as in the array. clazz will insert dependencies from the array as arguments into the class closure. Dependencies are then available anywhere in the class closure, including constructor.
In fact, since dependencies are introduced into the prototype, they are available for static methods even before any object is created from the class. It is very convenient for connecting your applications or devices and end-to-end tests. It also eliminates the need to blow singletones into constructors that would otherwise unnecessarily knock down constructor code.
Check out this script: http://jsfiddle.net/5uzmyvdq/1/
Feedback and suggestions are most welcome!