Advanced Object Oriented JavaScript

"Object Oriented" Programming in JavaScript

Object oriented programming (OOP) is a programming paradigm that employs modularity, encapsulation, and abstraction to create software that is more reliable and easier to maintain. JavaScript, is a flexible, dynamic, interpreted language with very loose typing. In JavaScript, EVERYTHING is an object -- including variables and functions. Still, it is possible to write javaScript code in a "non-object oriented" fashion (having global variables and functions and code that is NOT easily maintainable). This page discusses how to write object oriented JavaScript code.

Review of Object Oriented Terminology

  • Namespace: A container which lets developers bundle all functionality under a unique, application-specific name. In Java, we would call this a package. In JavaScript, we can use a function as a namespace (and define other private/public functions inside of this namespace function).
  • Property: An object characteristic, such as name, GPA, birthdate.
  • Method: A function that is associated only with a particular type of object. For example, students might have methods like matriculate, register, graduate, etc.
  • Constructor: A method which is called to instantiate (initialize) an object. In Java, the constructor method has the same name as the class. In my JavaScript examples, I usually have a methods named like "MakeStudent" and these methods return an object (with properties populated and methods associated).
  • Encapsulation: You make your code "less breakable" by hiding the data (properties) and methods of an object. By exposing only the methods that you want other programmers to call, your code is easier for them to use and harder for them to misuse.
  • Abstraction: Abstraction is a very general term that describes the desired qualities of making your code:
    1. more reusable (can operate on more than just one specific class), and
    2. fool proof - a programmer cannot misuse your class because you have hidden the properties and methods that are not needed outside the class.

Prototype-based Programming

Prototype-based programming is also called classless, prototype-oriented, and/or instance-based programming. This style of programming has been growing increasingly popular lately. In JavaScript, every object inherits from another object, the parent of an object is called its "prototype". By referencing and modifying the parent of an object, you can affect ALL of its children objects. In this way, the parent object can play the role of "class" to its children objects. Click here for a very good explanation of JavaScript prototyping and how it can be used to achieve many of the goals of OO programming. You are not responsible for knowing how to use the JavaScript "prototype" keyword. Modifying prototypes is something that can be very dangerous (you might redefine very basic JS functionality), so it is recommended only for very advanced JS programmers.

In JavaScript, Data Type is Dynamic and "Function" is a Data Type

In JavaScript, the data type of a variable is the type of the last value assigned to it. JavaScript "data types" include the normal low level data types like integers, real numbers, strings, and programmer defined objects. But you may be surprised to learn that functions are also treated like "data types". The code example below demonstrates these concepts:

    var o = 5;                 // Declare object o. It's "type" is now integer (because of its current value).
    o = "sally";               // o's type was just changed to String (object).

    // Change o's type to be a function (that adds two numbers), then call that function.
    o = function (i, j) {
        return (i + j);
    };                         // semi-colon ends the multi line statement
    console.log("Calling function o with 3 and 2 as inputs: " + o(3, 2));       // o(3,2) evaluates to 5

    // The above function declaration could also have been written using "normal syntax".
    function o(i,j) {
        return (i + j);
    }                          // no semi-colon here
    

Important Tip: Use "var" When Declaring Variables (or Objects) in JavaScript

Unfortunately, javaScript variables (including arrays and objects) that are used without being declared (with the keyword "var") are auto-declared as global, even if their first use is inside a function. Having a lot of global variables obviously is a bad programming practice, especially under situations where different code bases are trying to work together. Also, you would not want to unwittingly declare a global variable by misspelling a variable that you previously declared. In order to prevent this problem, include the "use strict" javaScript directive (which is supported by most newer browsers). Once your javaScript code executes the "use strict" directive, the browser will then throw an exception if ever your code references a variable which was never declared. Here is an example:

    "use strict";      // forces you to always use declare an object using "var" for type like this: var i = 5;
    doesNotExist = 42; // newer browser will throw a Reference Error
    

  • Click here for reference (from stackoverflow.com).

New (ES6) alternatives to var: let and cons

let is a new ES6 alternative to var. Like var, let declares a variable (or object), but with block scope (like java), instead of the function scope that you get when you use var.

If you want to declare a constant, you can use the keyword const. If you later try to assign a new value to a const, the browser will error out.

Standard Built-in Objects

JavaScript has several objects included in its core, for example, there are objects like Math, Object, Array, and String. The example below shows how to use the Math object to get a random number by using its random() method.

    console.log(Math.random());
    

Note: The console.log() function is not actually a part of JavaScript itself, but most browsers implement it to aid in debugging.

Simple JavaScript Objects

Here is an example that shows a simple JavaScript object, how it is created, how it can be used, and how it's methods and properties can be dynamically created/modified at any time. In this simple example, all properties and methods are public, and therefore modifiable by any code that has a reference to the object. This is not achieving the OO encapsulation that is desirable.

    // o is an object with two (public) properties.
    var o = {name: "sally", age: 23};

    // Set o to be an object with 2 properties and a method (all public), then invoke that method.
    o = {
        name: "sally",
        age: 23,
        sayHello: function () {
            console.log("Hello from " + this.name);
        }
    };
    o.sayHello();                                    // Writes "Hello from sally"

    // Redefine the object's sayHello method.
    o.sayHello = function() {
        console.log("Shout out from " + this.name);
    }
    o.sayHello();                                    // Writes "Shout out from sally"

    // Reference public property of o
    console.log("You are " + o.age + " years old");  // writes "You are 23 years old"

    // Modify public property of o
    o.age=o.age+1;
    console.log("Now you are " + o.age);             // writes "Now you are 24"

    // Dynamically add a method to object o, then invoke that method
    o.sayGoodbye = function () {
        console.log("Goodbye from " + this.name);
    };
    o.sayGoodbye();                                  // Writes "Goodbye from sally"
    

-> Tip: Use an Object as Method Input Parameter (not an ordered list of parameters)

Instead of having a parameter list (passed to a method), it is better to pass in an object that has the parameters as properties. This makes the code much more readable, because the parameter names can easily be seen in the calling code and in the called code. The called code can check which parameter properties have been specified and then set the other (unspecified) parameter properties to default values. In the example below, the logical or || is used to determine if the property exists (and if so, uses it), else sets it to a default value.

function myMethod(params) {

    // if any of the param properties do not exist, set them to default values.
    params.name = params.name || "no name";
    params.fontSize = params.fontSize || 12; 
    params.value = params.value || 0;
    // ...
}

// MAIN PROGRAM

myMethod( 
    {
        name: 'sally', 
        // don't care about fontSize, so don't specify (you'll get 12, default value)
        value: 24 
    } );
    

Encapsulation (Data Hiding) in JavaScript Using Closures

Encapsulation means packaging data and methods into a single unit and hiding implementation details. It is common in other object oriented languages to have private and methods and properties, but with JavaScript, you simulate private properties by using "closures". A closure is a function having access to the variables in its parent scope, even after the parent function has closed (finished executing). This pattern allows you to create objects with methods that operate on data that isn't visible to the outside world. It should be noted that data hiding is the very basis of object-oriented programming.

The following example shows how a function's local variables (counter and name) can be accessed after the function has finished executing.

// Press F12 in Chrome and Click "console" to See Output  

function MakeCounter(counterName, initValue) {

    /* The scope of these variables (counter and name) is LOCAL to function
     * "MakeCounter". They are not accessible outside of this function.  */
    var count = initValue;
    var name = counterName;

    /* function MakeCounter returns an object consisting of two functions
     * (increment and print) that can access their parent's private
     * properties (count and name) after the parent function (MakeCounter)
     * has "closed" (finished executing). */

    counterObj = {}; // empty object. Below add a public method and return. 

    counterObj.increment = function () { // public method
        count++;
        print(); // call private method print
    };

    function print () { // private method, accessible only inside MakeCounter
        console.log(name + " value is " + count);
    };

    print(); // initial printint of name and count

    return counterObj;
}

// MAIN PROGRAM
var first = new MakeCounter("first counter", 10); // prints: first counter value is 10

first.increment(); // prints: first counter value is 11

var second = MakeCounter("second counter", 100); // prints: second counter value is 100

first.increment(); // prints first counter value is 12

second.increment(); // prints second counter value is 101

console.log("private property: " + second.count); // error: count is a private property

// would not get this far since previous line is error and would halt execution, but if it would...
console.log("private property: " + first.print); // error: print is a private function 

    

  • Click here for published, working sample code (must run in Chrome, press F12 for debugger, and click on the Console tab to see the program's output).
  • Click here for reference (from tutorialspoint.com)

JavaScript Namespace

A namespace (like a java package) is a container which allows developers to bundle up functionality under a unique, application-specific name. Namespaces help avoid name conflicts between two different code sources. In JavaScript a namespace is just another object that contains methods, properties, and other objects.

Note: Unlike other programming languages, JavaScript does not distinguish between regular objects and namespaces. This can be a point of confusion for new JavaScript programmers. The idea behind creating a namespace in JavaScript is simple: instead of creating a bunch of global objects and functions, add all those objects and functions to a single object that acts like a namespace.

In the example below, function MakeApp creates an empty object/namespace (called app), adds the MakeCounter function to that namespace and then returns the namespace. Function "MakeCounter" is the same as shown in the previous example. The main program is functionally the same as the previous example - it just has to get a reference to the namespace first, then call namespace.MakeCounter instead of just calling MakeCounter. The changes/enhancements from the previous example have been highlighted.

function MakeApp() {

    function MakeCounter(counterName, initValue) {

        /* The scope of these variables (counter and name) is local to
         * function "adder", not accessible outside of his function.  */
        var count = initValue;
        var name = counterName;

        /* function makeCounter returns an object consisting of two functions
         * (increment and print) that can access their parent's private
         * properties after the parent function has "closed" (finished executing). */

        counterObj = {}; // empty object, then add two methods to it 
                         // (increment and print)
        counterObj.increment = function () {
            count++;
        };
        counterObj.print = function () {
            console.log(name + " is " + count);
        };
        return counterObj;
    }
    
    var app = {};
    app.MakeCounter = MakeCounter;
    return app;
}

// MAIN PROGRAM  
var myApp = MakeApp();
var first = new myApp.MakeCounter("first counter", 10);
first.print(); // prints 10

first.increment();
first.print();  // prints 11

var second = new myApp.MakeCounter("second counter", 100);
second.print();  // prints 100

first.increment();
first.print(); // prints 12

second.increment();
second.print();  // prints 101

// prints "private property undefined"
console.log("private property: " + second.counter);
    

  • Click here for published, working sample code (must run in Chrome, press F12 for debugger, and click on the Console tab to see the program's output).

Dependency Injection

While "Dependency injection" may sound very complicated, the concept is very simple. When code A calls code B, we say that code A "is dependent upon" code B. In other words, if code B does not exist, code A will break due to an unresolved reference.

To avoid unwanted dependencies, we make a rule called dependency injection. We consider consumer code (e.g., the HTML page that wants to use a reusable component) and provider code (e.g., an external JS file that creates a reusable component).

  • Good scenario (follows dependency injection principal). HTML page invokes a component passing in a reference to a div on the HTML page. The HTML page is "asking the component to manage" the div with id "counterDiv".
        // This is in the HTML page...
        var myCounter = MakeCounter("counterDiv");  
                
  • Bad scenario (does not follow dependency injection principal). The component does not ask for the id to manage and the HTML page does not pass in such a reference. Instead, component code (in the external JS file) assumes that the HTML page will have a specific id and references that "hard coded" id.
        // This is in the component, in the external JS file
        document.getElementById("pageComponent").innerHTML = "Counter value is " + count;           
                
    In the above example, if the HTML page does not have a DOM element with id "pageComponent", the code will break. The component is not reusable. The component did not follow the dependency injection principal.

Creating a "JavaScript Component"

A "JavaScript Component" (at least my definition for our course) implies encapsulated code that exists in a separate JS file and that is offered up to other programmers so that they may use the code without having to understand its internal workings. This style of coding takes advantage of all the object oriented techniques described above - using namespaces, having private variables and private methods, use of input parameter objects instead of parameter lists, dependency injection.

"Counter Component" Example (Creates Counter Objects that have a Visual Component)

The "Counter Component" given above uses the following techniques:

  1. The JavaScript code is placed in an external JavaScript file (that programmers / consumers of your component would reference).
  2. JavaScript namespaces avoid naming conflicts (same concept as java packages).
  3. Input parameter objects are used, instead of ordered lists of input parameters - so that it is easier for the component consumer (programmer) to pass in the correct parameters for their desired result.
  4. Dependency injection is used, meaning that the component (provider code) does not assume the existence of anything in the HTML page (consumer code) EXCEPT what the HTML page passed to the component in the first place. "hard code" any reference to the code that is consuming the component. In this example, the HTML page passes a parameter object to a constructor method within the component. One of the possible properties of that parameter object is the name of a function (within the HTML page) that the component should call under error conditions. If the HTML page specifies a function name in the "errorCallBackFn" property of the parameter object, the component will that specified function if it beieves that size of the visual counter is too small -- this is just trying to demonstrate the idea of dependency injection.
Click here to run sample code that creates two counter objects that have a visual component. These "visual counters" increment when you click them. After running the code, "View Source" to see the HTML code (that uses the "component"), then click on the linked JS reference to see the JavaScript that is the "component". Click here for zip file.

"Validater Component"

In the following (increasingly sophisticated) examples, I attempt to demonstrate how to create a very simple JavaScript "Component" that knows how to validate user input (check if numeric, etc). In each example, run it first, then View Source (on the consumer code), then click on the link of the JavaScript code reference (from View Source) - to see the component code.

  • In this version, the JavaScript file just defines an object with reusable (validation) methods inside. The JavaScript file arbitrarily decides on a name for this one object, so it is "polluting the global namespace", but only by creating a single object name.
  • In this version, the JavaScript file accomplishes exactly the same thing, but uses a different syntax to assign functions (methods) to the object.
  • In this version, a JavaScript function passes back that the object (with validation methods inside). This allows the HTML file to name the object that is returned (that holds the validation methods) - so NO namespace pollution !
  • In this last version, the JavaScript function takes (as input parameter) an array of JavaScript objects where each object defines a validation to be done (e.g., id of the textbox to be validated, type for the validation, id of the HTML element where the error message is to be displayed). In this version, the JavaScript file can "reach" out to the client's page elements and perform very useful tasks.
  • Click here for a zip file of all the code examples provided above.

More JavaScript "Component" Examples

  • In this code, we have an "app" namespace that encloses two "make" functions, one that creates a counter and one that creates a balance calculator (based on interest rate). By placing these two "make" functions inside of the single app function, the two make functions can share code that does not have to be global. By avoiding global declarations, we avoid name conflicts. Click here to run (View Source for code).
    • Click here for zip file.
  • In this "widget" component, I've played around with having the HTML page pass in call back functions. Click here to run (View Source for code). In this code, I have also made a link from my component code (widgetFW.js) to another js file (makeColor.js).
    • Click here for zip file.
  • To see my "lightbox" component, click here to run (View Source for code).
    • Click here for zip file.
  • This version of my "lightbox" component allows the HTML coder to place (and style) the images using HTML/CSS. The params object (that's input to the MakeLightbox method) no longer has a property that's an array of image names. The component (in the external JS file) simply looks for all the <img> tags within the id that was passed in by the HTML page.
        lightBox.getElementByTagName('img'); 
                
    The JS code does not have to create the image elements and append them to the lightbox DIV. Click here to run (View Source for code).
    • Click here for zip file
    .