Object Oriented JavaScript
The JavaScript language is simple and straightforward and often there’s no special syntax for features you may be used to in other languages, such as namespaces, modules, packages, private properties, and static members. For this reason, a lot of ambiguity lingers thought the streets of JavaScriptville.
Before we start creating classes, I would like to introduce the concept of namespaces. Namespaces help reduce the number of globals required by our programs and at the same time also help avoid naming collisions or excessive name prefixing. JavaScript doesn’t have namespaces built into the language syntax, but this is a feature that is quite easy to achieve. We'll create a global function, which will create a namespace according to the received fully qualified namespace name (e.g. JsDeepDive.Common.Managers). We'll iterate over each segment of the namespace and create namespaces where are needed. Full implementation looks like this:
Namespaces
Before we start creating classes, I would like to introduce the concept of namespaces. Namespaces help reduce the number of globals required by our programs and at the same time also help avoid naming collisions or excessive name prefixing. JavaScript doesn’t have namespaces built into the language syntax, but this is a feature that is quite easy to achieve. We'll create a global function, which will create a namespace according to the received fully qualified namespace name (e.g. JsDeepDive.Common.Managers). We'll iterate over each segment of the namespace and create namespaces where are needed. Full implementation looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | function namespace(namespace) { "use strict" ; var object = window, tokens = namespace.split( "." ), token; while (tokens.length > 0) { token = tokens.shift(); if ( typeof object[token] === "undefined" ) { object[token] = {}; } object = object[token]; } return object; } |
1 2 3 | var JsDeepDive = JsDeepDive || {}; namespace( 'JsDeepDive.Common.Managers' ); namespace( 'JsDeepDive.DAL' ); |
Static Classes
Now that we know how to create namespaces, let's see how to create static class. Logger, utils, factory pattern are usually implemented as static classes, which are easily created using Revealing Module Pattern. If you'd like to grasp a deeper knowledge about the pattern, have a look at Carl's Danley article.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | var JsDeepDive = JsDeepDive || {}; namespace( 'JsDeepDive.Common' ); JsDeepDive.Common.Utils = function () { "use strict" ; var privateMember = 1, privateFuncA = function privateFuncA() { return 'privateFuncA' ; }, privateFuncB = function privateFuncB(test) { return test === privateMember; }, privateFuncC = function privateFuncC(test) { if (privateFuncB(test)) { return 'test passed' ; } else { return 'test failed' ; } }; return { publicFuncA: privateFuncA, privateFuncC: privateFuncC }; }(); |
Let's check our class and call its members:
1 2 3 4 5 | var Utils = JsDeepDive.Common.Utils, c = console; c.log( 'Utils.publicFuncA returned: ' + Utils.publicFuncA()); c.log( 'Utils.privateFuncA is ' + typeof Utils.privateFuncA); c.log( 'Utils.privateMember is ' + typeof Utils.privateMember); c.log( 'Utils.publicFuncA returned: ' + Utils.privateFuncC(1)); |
Utils.publicFuncA returned: privateFuncA Utils.privateFuncA is undefined Utils.privateMember is undefined Utils.publicFuncA returned: test passed
Instantiatable Classes
There are 2 main ways to create instantiatable classes in JavaScript. One is through public methods, the other is by using prototype attribute. You can see the syntax difference in the example below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | function ConstructorPublicMethods() { var privateMember = 1; this .publicMethod1 = function publicMethod1(n) { return 2 * n; }; this .publicMethod2 = function publicMethod2(n) { return 3 * n; }; } function PrototypePublicMethods() { var privateMember = 1; } PrototypePublicMethods.prototype = { publicMethod1: function publicMethod1(n) { return 2 * n; }, publicMethod2: function publicMethod2(n) { return 3 * n; } }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function publicMethod2(n) { var c = console; c.log( 'privateMember: ' + typeof privateMember); c.log( 'this.privateMember: ' + typeof this .privateMember); return 3 * n; } /* testing the method */ console.log( 'testing PrototypePublicMethods' ); var obj = new PrototypePublicMethods(); obj.publicMethod2(2); console.log( 'testing ConstructorPublicMethods' ); obj = new ConstructorPublicMethods(); obj.publicMethod2(2); |
testing PrototypePublicMethods privateMember: undefined this.privateMember: undefined testing ConstructorPublicMethods privateMember: number this.privateMember: undefinedSo how can we use prototype method and still being able to access the private members? The trick is to expose a method using the public method way, but to limit it's accessibility to the outsiders. The exposed method will have the access to all the private members as desired and calling it from the prototype exposed methods will provide them the new ability. Little confused? Let me show the example presenting the case.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | var JsDeepDive = JsDeepDive || {}; namespace( 'JsDeepDive.Common' ); ( function (key) { "use strict" ; JsDeepDive.Common.Manager = function () { /* Start private parameters and functions of the class */ var privates = { privateMember: undefined, privateFuncA: function privateFuncA() { return 'privateFuncA' ; }, privateFuncB: function privateFuncB(test) { return test === this .privateMember; }, privateFuncC: function privateFuncC(test) { if ( this .privateFuncB(test)) { return 'test passed' ; } else { return 'test failed' ; } }, _constructor: function _constructor() { console.log( '_constructor is called' ); this .privateMember = 1; } }; /* End private parameters and functions of the class */ this ._ = function (aKey) { return key === aKey && privates; }; privates._constructor(); }; JsDeepDive.Common.Manager.prototype = { publicFuncA: function publicFuncA() { return this ._(key).privateFuncA(); }, privateFuncC: function privateFuncC(test) { return this ._(key).privateFuncC(test); } }; }({})); |
Static Methods
Once we can create static and instantiatable classes, creating static methods is a trifle. All methods of static classes are static as well, so this does it. When it comes to instantiatable classes, you may simple assign the method to the class name and add the implementation. Following the preceding example, the described can be achieved as following:
1 2 3 4 5 6 7 8 | JsDeepDive.Common.Manager.staticMethodA = function staticMethodA() { return 'this is static method' ; }; /// somewhere in the code console.log( 'staticMethodA: ' + JsDeepDive.Common.Manager.staticMethodA()); |
staticMethodA: this is static methodHope you enjoyed the article and feel free to leave your comments below.
Comments
Post a Comment