Introduction
Modularization and encapsulation are two important concepts in software development. While both of these concepts are relatively easy to implement in most programming languages, implementing them in JavaScript is not intuitive and requires a more complete understanding of the language itself. The specifications for EMCAScript 2015 (formerly EMCAScript 6) does make implementing these concepts much easier and inline with other languages; however, browser support for EMCAScript 2015 is still incomplete. Modularization can also be achieved with third-party frameworks like Browserfy and RequireJS, which use the CommonJS and Asynchronous Module Definition (AMD) patterns of JavaScript modularization.
Background
Encapsulation is most commonly associated with object-oriented programming (OOP) but the concept itself is in fact separate from it and can be implemented in the absence of any OOP pattern, including prototypal, as is the case in JavaScript. The two notions that define this concept are:
- bundling the data with the methods of a module and
- restricting direct access to a module's components
The intent of the first notion is to prevent the user of the module from setting the module into an invalid or inconsistent state that was not intended by the module's creator. Both notions make code more understandable and maintainable which can be considered the most important concept of all, particularly from a developer's point of view.
Modularization provides another way to develop code that is more understandable and maintainable. In addition, it prevents real-time issues that can occur with namespaces by keeping components (and associated code) out of the global namespace where one code component may inadvertently override the implementation of another if the names are the same (for example, two functions with the same name). Using third-party frameworks and libraries can really increase the potential for these issues which is why most of them implement modularization.
The Code
In order to demonstrate a real-world example of these concepts, I have created a very usable set of components that partially mimic the .NET implementation of a generic list collection with LINQ support (see this link for an understanding of LINQ with .NET). The code for this is attached to this article. Most of the code used below in this article is taken directly from this implementation.
Modularization is achieved with the use of anonymous function expressions as opposed to function declarations. As seen in the code below, adding a set of parenthesis at the end of the function definition will make it a function expression which is executed immediately when the code is loaded (any functions inside the module are NOT executed immediately). An anonymous function in and of itself will not provide modularization since no 'name' is provided in the namespace (in this case the global
namespace), which is why we declare a global
variable (in this case, it is named 'Zenith
'), pass that variable into the anonymous function and assign any components to it in the function implementation. The important idea to take away here is that the name 'Zenith
' is added to the global
namespace and is required to access any of the components inside of it; thus, keeping these components out of the global
namepsace. Also note that nothing prevents the developer from creating namespaces within namespaces which could keep an entire namespace out of the global
scope.
var Zenith;
(function (Zenith) {
function List() {
};
Zenith.List = List;
})(Zenith || (Zenith = {}));
In the above example, the List
function can only be accessed in JavaScript code by first providing the Zenith module name as follows:
var list = new Zenith.List();
Note the code inside the parentheses at the end of the function definition:
Zenith || (Zenith = {}
This pattern allows you to add any number of components to a namespace just by adding additional anonymous functions and passing the same global variable (acting as the namespace name) in as a parameter. For example:
(function (Zenith) {
function LinkedList() {
};
Zenith.LinkedList = LinkedList;
})(Zenith || (Zenith = {}));
Now, in addition to creating a List
component in the same code base, we can also create a LinkedList
component as follows:
var list = new Zenith.LinkedList();
You may hear different terms for different module patterns. Strictly speaking, this pattern is called the Revealing Module Pattern because all of the components inside the module are by default private
(and cannot be accessed from outside the module) but assigning these components to the 'outside' (global
) variable makes them public
.
Encapsulation can be achieved using functions in JavaScript and it requires a partial understanding of the this
keyword in JavaScript. Please note that the this
keyword is more complicated than its use described here. When used in a function, it refers to the object that was created using the function. This is easier to understand when you realize that a function
is actually an object in JavaScript. Here is an example:
function List(initialArray) {
var listArray = initialArray || [];
this.Count = function () {
return listArray.length;
};
}
var list = new List();
var list2 = new List();
In the above example, list
and list2
end up as variables that refer to two different objects created using the List
function. Each one contains a listArray
variable and a Count
method. The this
property will refer to the corresponding object. (In this example, it is actually unnecessary and wasteful to create the Count
method for every object since the code will be the same for every object; however, avoiding this requires the implementation of prototypal inheritance in JavaScript which is outside the scope of this article).
As you can see, the first notion of encapsulation is achieved since the data and method is bundled with each object. The second notion is not so intuitive here but is nonetheless easy to implement with just a little bit of knowledge. Using the this
keyword (property) when defining a property or method makes the property or method public
, or accessible from outside the function. All variables or methods not defined using the this
keyword are private
. So, in the above example, the variable listArray
for each object is private
and the Count
method is public
. Simple as that!
History
- 10/3/2017: Initial version submitted for approval
- 10/4/2017: Second version submitted for approval