Our Javascript O.O. Patterns - ΩJr. Software Articles and Products

This information lives on a web page hosted at the following web address: 'https://omegajunior.globat.com/code/'.

We use a combination of static and initiable objects, most of which gets written as JSON. Our static objects contain an method named init, which gets called by the browser, because we program it that way in the web page. A small exploration.

Why this article

Recently we came across a question on Reddit, in which the question was posed how to make a javascript program more Object-Oriented. We started out discussing how we do that, and then decided that it was worth its own Knowledge Base article.

This is that article.


A bit of history

Object-oriented programming has been discussed in text books since about, let's estimate, 1985. Javascript got released to the public around 1995, which is a good decade later. By design it sports several coding paradigms, including an object-oriented one. (It also supports imperial programming, event-driven programming, functional programming, and maybe others.)


Inside / Outside; mixed paradigms

When addressing browser elements, we necessarily address them in an object-oriented way. The global object is the window, which has methods and properties, one of which is the document, which contains methods and properties, amongst which we find all the HTML elements of the web page.

But what does that mean: "global object"? Well, that is the most outer programming context that our javascript programs are allowed to touch. That is where we tell the javascript parser / interpreter in our web browser where our application starts.

In case of Node.js, the global object is not a browser window, but a web server. In case of MongoDB, the global object is a database. And in a web browser it is the browser window.

We inform the browser by javascript programming which we necessarily place outside of our own javascript application. That is because the Javascript language is not entirely object-oriented, and does not provide an equivalent of the "main" methods found in c, Java, and VB.

Traditionally, any javascript coding found in an inline script gets executed when the javascript parser is reading it.
<script>var myGame = ({});</script>
This is called Imperial Programming. It is neither event-driven, nor object-oriented, nor functional. It is necessary when declaring functions and objects that use any of those other programming paradigms.

Starting our own application

Usually, we add a small bit of imperial code to the HTMLBodyElement, like this:
<body onload="myGame.init()">

This is an obtrusive way. The same coded unobtrusively looks like this:
<script>
window.addEvenListener("load", myGame.init, false);
</script>


The way it is written and executed is still imperial, but the statement uses the object-oriented qualities of both the global window object and the myGame object. And it also sets up event-driven programming, which gets started automatically as soon as the window has been loaded.

There are a couple of drawbacks using the unobtrusive approach:

Some browser manufacturors decided that their excuse for a web browser would not need a method addEventListener(), but implemented a method attachEvent() instead, forcing us to suffer through this difference, by using cross-browser libraries that test it for us:
<script>
if ( window.addEventListener ) {
window.addEvenListener("load", myGame.init, false);
} else if ( window.attachEvent ) {
window.attachEvent("onload", myGame.init);
} else {
window.onload = myGame.init;
}
</script>


To be truly unobtrusive we should test for the existence of the objects window, my, and my.app, and of the methods window.addEvenListener and my.app.init, which tends to get quite verbose:
<script>
if ( ( !!window ) && ( !!my ) ) {

if ( ( !!window.addEvenListener ) && ( !!my.app ) ) {

if ( !!my.app ) {

window.addEvenListener("load", my.app.init, false);

}

}

}</script>


Luckily we can abstract the first issue by using cross-browser javascript frameworks like our own zjrJS:
<script>zjrJS.Doc.aE(window, "load", my.app.init);</script>


OnLoad vs. DOMContentLoaded

Now you may have noticed that we chose to kick off our app initialiser on window.onload, rather than on document.DOMContentLoaded, like JQuery does if possible. OnLoad happens after DOMContentLoaded, so why wait?

Well, basically, because DOMContentLoaded is part of the HTML5 specification and not all the browsers used can run HTML5. OnLoad however has been around for nearly 2 decades, so we can safely assume most javascript-capable browsers can understand and execute our code. If you use JQuery you should not have to worry about these particulars, as its $(document).ready() chooses which event to use based on the visitor's browser's capabilities.


Object Initialisers vs. Object Constructors

Most of the javascript apps we write is written as JSON and have an init() method per object. This was chosen because most of our apps are not data-driven. It makes sense to use objects with their own constructors when programming a data-driven app.


Our Object Initialisers

For instance, our typical game is structured as follows:
<script>var myGame = ({
Config: ({}),
Local: ({}),
UI: ({}),
Game: ({}),
Test: ({}),
init: function () {}
});</script>

This makes myGame a singleton: we expect there is going to be just 1 instance of myGame per web page, which gouverns everything that happens with the game. myGame gets initialised by the browser as soon as its javascript interpreter reads and parses the file.

A different way of coding the same is:
<script>var myGame = new Object();
myGame.Config = new Object();
myGame.Local = new Object();
myGame.UI = new Object();
myGame.Game = new Object();
myGame.Test = new Object();
myGame.init = function () {};
</script>


Which format you choose depends on personal preference as well as company policies on atomic and maintainable coding. The first drawback of the JSON formatting is that a single grammar mistake will void the entire application.

Now some may argue that the structure above does not reflect the "S" concept from the SOLID principle for Object-oriented design, which more or less states that each object should be responsible for its own well-being, and not for others. Well, we chose to make a single UI object responsible for displaying anything and everything, rather than having each data object instance display itselve. So in our case, the data is agnostic of its display.

Whether this bothers you or not mostly is a matter of personal preference, and sometimes a matter of conformity to company policies and how the application is used and maintained.


Object Constructors

For data-driven apps it might make sense to make each object aware of itself and responsible for how it behaves. A data object might look like this:
function Employee (name, address, payRate, manager) {
this.name = name;
this.address = address;
this.payRate = payRate;
this.manager = manager;
function displayAddress ( how ) {
/* do something */
}
return this;
}


You would save this object in its own file named Employee.js.

Initialising an Employee object variable is this simple:
<script>
var aev = new Employee(
"André E. Veltstra",
"Street, Town, State, Zip",
EmployeePayRates.role3,
null
);</script>


Note how the Javascript language uses the function keyword for object statements with constructors, whereas other languages have particular keywords like class and interface. When using the Object() expression, we lose the option to assign our own constructor.


Neither Initialiser nor Constructor

Note how we secretly assumed the presence of a static EmployeePayRates object that enumerates pay rates? This helps normalise our data, which helps keep our data easily searchable and maintainable.

The EmployeePayRates object should have its programming in its own file named EmployeePayRates.js. Rather than a offering a constructor or an initialiser it simply exposes pay rates. One could use a JSON formatting for its declaration:
var EmployeePayRates = ({
none: 0,
role1: 1,
role2: 2,
role3: 3,
role4: 4
})

Now using the names "role1", 2, 3, and 4, is not descriptive enough, and you should make them as descriptive as possible, based on the domain of work and the semantics used in that domain.

Notice how in this particular example, the pay rates are set as an ordinal number, rather than an actual value. Whether or not you should use this approach or actual monetary values, depends on where the actual monetary values are stored which location is cardinal, and which methods are going to use that information.

For instance, we can imagine storing the cardinal pay rate values in a database, but caching that information into a memory object for reasons of speed. We can also imagine keeping the pay rates as ordinal numbers when the employee information is stored back into a fully normalised RDBMS.

It is not entirely true that the EmployeePayRates object has neither constructor nor initialiser. It does... but it does not offer that to the programmer in the same way the Employee object above did. The EmployeePayRates object is initialised into memory as a singleton by the browser's javascript interpreter right after reading and parsing its script.

In our own javascript programming you may have seen that the Config object is like the sample EmployeePayRates object above: a singleton that merely exposes settings to the rest of the application. The Local object also is a singleton that exposes settings (in our case lingual translations of dialogue sentences and verbs), but tends to do so via a method that automatically chooses the correct language.


JSON: no O.O. Obfuscation

The 2nd drawback of JSON formatting is that it provides neither protection nor obfuscation of our objects. That means that an unsuspecting programmer runs the risk of ruining our objects and destroying our app unintentionally. We'll discuss O.O. Protection below.

The reason why a pure JSON formatting provides no obfuscation is that it does not allow for the declaration of a return statement. As soon as we add a return statement, the formatting no longer is pure JSON. So in our own JSON coding we find immediately executed singleton declarations like this:
zjrJS.Doc.Store = (function (){
var _save = function (key, value) {};
var _read = function (key) {};
var _supportsLocalStorage = function () {}
var _supportsCookies = function () {}
var _saveToLocalStorage = function (key, value) {}
var _saveToCookies = function (key, value) {}
var _readFromLocalStorage = function (key) {}
var _readFromCookies = function (key) {}
return ({get: _read, set: _save});
}());


This way, an implementation partner talking to the zjrJS.Doc.Store object only sees the functions get and set, and does not see any of the other functions. We are hiding complexity inside our object, and making use of it as simple as possible.

The reason it works, is that the return statement encapsulates and returns only some of the declared functionality in a closure. (Prefixing some function names with an underscore is only a visual clue for us, and has no influence on the obfuscating mechanism).


Javascript: No O.O. Protection

In an object-oriented language like Java, the private properties and methods of a class can be accessed only by the class itself. That means that an object instance of a class accesses its properties by getter and setter methods defined in the class. If there is no getter for a certain property, the object instance cannot read it.

In Javascript however, there is no such protection. This makes it extremely important for a javascript program to be written in a way that is clearly understood, and explains its own intentions. Otherwise an implementation partner runs the risk of overwriting essential parts, damaging the data and the application.

This lack of protection is part of the design of the language. It allows for extension of everything. We can add new properties and methods to our own objects as well as the built-in ones in the language, but also those exposed by the browser. This is how a cross-browser library like Prototype is able to extend browser functionality: it adds new methods to the prototypes of basic, built-in language types and objects.

One of the ways we avoid this problem is by fully qualifying the methods and properties we define, and using "namespace" objects that encompass our other objects. Javascript does not define real namespaces, so we are forced to encapsulate our objects within a larger object, like the myGame object in our example above.

We also make sure to avoid declaring new methods and properties in the global object (window, in case of a web browser), thereby avoiding overwriting other objects' properties by mistake. We declare them inside objects instead, and make sure to address them using their fully qualified object hierarchy: rather than
forms.login.elements.name = "aev";
we go for:
window.document.forms.login.elements.name = "aev";
and to shorten this and speed up execution we write this:
var login = window.document.forms.login;
login.elements.name = "aev";
login.submit();



Conclusion

We have shown several ways to use javascript in an object-oriented way. We have shown that imperial programming and event-driven programming not just possible too, but also that these programming paradigms benefit from each other. Finally, we have shown how and explained why we format our programming the way we do.

We hope that this article was helpful for your javascript undertakings. Is there anything you would do differently?

Need problem solving?

Talk to me. Let's meet for coffee or over lunch. Mail me at “omegajunior at protonmail dot com”.