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:
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; }Using this method is easy. The following code will create namespaces JsDeepDive.Common.Managers and JsDeepDive.DAL. The first line ensures to create our parent namespace, if it hasn't been created by previous modules.
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.
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 }; }();Here we created namespace JsDeepDive.Common and wrote a new static class, called Utils. In it we created 3 private methods and private member. Please notice how privateFuncB uses the member and the method itself is called from privateFuncC. In the bottom of class declaration we expose privateFuncA through publicFuncA and privateFuncC with the same name. Both privateFuncB and privateMember remain unreachable from outside the class.
Let's check our class and call its members:
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));The output of out tests will be:
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:
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; } };Here we created 2 classes using different methods. So which method is better? The prototype one and the reason is performance. The prototype assignment is much faster than merely creating public function using assignment operator. To be specific 99% percent slower and you can read all about it here. So why is there so much confusion you may ask? The problem with prototype method is inability to access private members from the public methods. Have a look at privateMember and let's try to access it from publicMethod2 using both methods by rewriting the method.
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);Running the tests will prove the hypothesis and show that privateMember is undefined:
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.
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); } }; }({}));As you can see all the private methods are encapsulated within the privates member. Only methods defined within the class's scope can access them, specifically our tunnel method _, defined in line 34. This is a public method and can be accessed by anyone, but only the ones who know the secret key, can get the privates collection; others will always receive null. Please notice that we initiated the key with empty anonymous object - {}. This way no one will never be able to get the correct key. The strict equal operator, which is used while comparing the keys, ensures that no matter what we put in the aKey parameter, key === aKey is true only for the same object.
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:
JsDeepDive.Common.Manager.staticMethodA = function staticMethodA() { return 'this is static method'; }; /// somewhere in the code console.log('staticMethodA: ' + JsDeepDive.Common.Manager.staticMethodA());Which will print:
staticMethodA: this is static methodHope you enjoyed the article and feel free to leave your comments below.
Comments
Post a Comment