Design Patterns For Object-Oriented JavaScript

Design Patterns For Object-Oriented JavaScript

I completed a refactoring for our ERP’s JavaScript codes which was developed previously in functional way. Our target was to convert functional code mess to object-oriented one similar to our server-side Java codes. Years ago I had inspected some object-oriented JavaScript codes but I’d found it very complex to implement them in our library. However, today JavaScript is living its golden age and that couraged me to reconsider object-oriented techniques. 

I read some books about writing object-oriented codes and collected some useful patterns to use in my refactoring. I witnessed that some suggested patterns on those books are very complex and cumbersome to use. Here I’ll share some simple patterns I used during my conversion. It was very challenging that I had to keep existing functionaly but there were many script files and functionality. In existing functional code base, everthing was global and everthing was function. One reason that leads us to make such a refactoring was the necessity to use multiple instances of user interface components. Component states and behaviours were organized as file but that was not enough to keep states of multiple instances. Lastly, we were using Internet Explorer’s old HTC files for components and it is not supported anymore. 

After making refactoring the result was amazing that I didn’t even expect so. Before that work, I was assuming that I know JavaScript good enough to write solid code but I concluded that I was wrong. Although object-orientation in JavaScript is not as good as Java and many of them are work-arounds, it is still advantagous compared with functional JavaScript codes. 

Here are the patterns I used in the refactoring (I tested them with latest versions of IE, Firefox and Chrome):

1. Web Coding Principles


Principles I used during my refactoring are following:
  • Feature-Detect Rather Than Browser-Detect
  • Separation of concerns (Structure + Style + Behavior -> HTML + CSS + JavaScript)
  • Avoid Cluttering The Global Namespace
  • Modularization: Lazy Loading.

2. Three Ways of Function Creation


Let’s start with basics. There are 3 ways to create a function, one can use where it is appropriate (Note that a function is also an object):

With Function Declaration:

It is evaluated at parse-time, and can be used anywhere in the current scope.
 function myFunction(){
  console.log('hello');
 }
 myFunction(); //hello
 

With Function Constructor:

It is evaluated at run-time and can be used after it is created.
 var variablename = new Function(Arg1, Arg2..., "Function Body");

 var myFunction = new Function("console.log('hello')");
 myFunction(); // hello

 var myFunction = new Function("x", "y", "return x*y;");
 console.log(myFunction(4,3)); // 12
 

With Function Expression:

It is evaluated at run-time and can be used after it is created. It is also named as “Anonymous Function”.
 var myFunction = function(){
  console.log('hello');
 };
 myFunction(); // hello
 
or
 (function(){
  console.log('hello');
 })(); // hello
 

3. Block Scope Pattern


Block scope means that variables defined within a curly bracket can not be accessed outside the bracket. JavaScript doesn’t support block scope. The only way to achieve block scope in JavaScript is to use anonymous function in such a way that once we create it we call it. In this way global namespace cluttering is also prevented. Block Scope in JavaScript:
(function(){
 ...
})();

if(true){
 var q = 10;
}

{
 var x = 10;
}

for(var i= 0; i < 5; i++){
 var y = i;
}

(function(){
 var z = 20;
})();

console.log(q); // 10
console.log(x); // 10
console.log(window.x); // 10
console.log(y); // 4
console.log(i); //5
console.log(z); // “z is undefined” error

4. Type Constructor Function Pattern


Every function is also a type and new instances from these functions can be created with “new” keyword. Function declaration behaves as constructor and also called “Constructor Function”. In constructor functions, prototype methods and fields can be accessed.
function MyType(data){
 this.data = data;
}

var myInstance = new MyType("test");
console.log(myInstance.data); // test

5. Constructor JSON Pattern


It is very easy to create objects with JSON. It is possible to create nested objects with JSON. To create object in standard way:
var myObject = {}; // or var emptyObject = new Object(); 
myObject.field1 = "value1";
myObject.childObject = {};
myObject.childObject.field2 = "value2";
myObject.method1 = function(){
 return "method1 called";
};

console.log(myObject.field1); // value1
console.log(myObject.childObject.field2); // value2 
console.log(myObject.method1()); //method1 called
To create object with JSON:
var myObject = {
 field1:"value1",
 childObject:{
  field2:"value2"
 },
 method1:function(){
  return "method1 called";
 }
};

console.log(myObject.field1); // value1
console.log(myObject.childObject.field2); // value2 
console.log(myObject.method1()); //method1 called

6. "that" Pattern


In inner functions of objects created from function constructors with “new” operator we may need to access to the uppermost function object. To achieve that, in uppermost function constructor, “this” is assigned to a variable which is named as “that”(some uses “self” as name). In this way, we can refer to the uppermost object with “that” variable in inner scopes.
function Test(){
 var that = this;
 this.testField = "parent";

 innerMethod();

 function innerMethod(){
  this.testField = "child";
  console.log(this.testField); // child
  console.log(that.testField); // parent
 }
}

var x = new Test();
Beware that if function is directly called, this refers to current “window” object not the function object:
function Test(){
 console.log(this.name); // I'm a window
 innerMethod();

 function innerMethod(){
  console.log(this.name); // I'm a window
 }
}

window.name = "I'm a window";
Test();

7. Public Function Pattern


Prototype methods “public” and can’t be restricted. Prototype methods are shared among instances and if it is possible, prototype methods and fields should be used for “public” access.
function PublicTest(){
 this.publicFunction1 = function(){
  return "test1";
 };
}

PublicTest.prototype.publicFunction2 = function (parameter) {
 return parameter;
};

var x = new PublicTest();
console.log(x.publicFunction1()); // test1
console.log(x.publicFunction2("test2")); //test2

8. Public Field Pattern


For public field need, prototype or instance can be used. Prototype fields’ values are copied to the new instances. If a new instance changes it, it is not reflected to the other instances.
function PublicTest(){
 this.publicField1 = "test1";
 this["publicField2"] = "test2";
}
PublicTest.prototype.publicField3 = "test3";

PublicTest.prototype.testRead = function(){
 console.log(this.publicField1); // test1
 console.log(this.publicField2); // test2
 console.log(this.publicField3); // test3
 console.log(this.publicField4); // test4
};

PublicTest.prototype.testWrite = function(){
 this.publicField3 = "test3 written";
};

var x = new PublicTest();
x.publicField4 = "test4 ";

x.testRead(); // test1 test2 test3 test4

var y = new PublicTest();
y.testWrite();
var z = new PublicTest();

console.log(y.publicField3); // test3 written
console.log(z.publicField3); // test3

9. Private Field Pattern


In JavaScript, there is no private field support by language. It can be achieved with “Closures”. Variables defined within constructor function can be used as private fields. Any methods using private fields should be declared in constructor function (Prototype methods can’t be declared in constructor function and used with private fields). Private methods setter and getter functions should be declared in constructor function.
function PrivateTest(){
 var _privateField = "FieldDefaultVal";
 
 this.getPrivateField = function(){
  return _privateField;
 };
 this.setPrivateField = function(newValue){
  _privateField = newValue;
 };
 this.otherPublicMethod = function(){
  _privateField = _privateField + " changed";
  return this.getPrivateField();
 };
}

PrivateTest.prototype.publicPrototypeMethod = function(){
 this.setPrivateField("Changed  from prototype method");
 return this.getPrivateField();
};

var x = new PrivateTest();

console.log(x.getPrivateField()); // FieldDefaultVal
console.log(x.otherPublicMethod()); // FieldDefaultVal changed
console.log(x.publicPrototypeMethod()); // Changed from prototype method

10. Private Function Pattern


They are also named as “Nested or Inner Functions”. Private functions are defined within another function and can’t be accessed from outside. Private functions can be declared in any part of function.
function Parent (parameter){
 var that = this;
 this.publicVariable = "bar"; 
 var privateVariable = "foo"; 
 this.publicVariable = privateMethod();

 function privateMethod() {
  console.log(parameter);
  console.log(privateVariable);
  console.log(that.publicVariable); // this refers to inner function, so that we reached the parent function object with "that"
  return "test";
 }
}
Or
function Parent (parameter){
 var that = this;
 this.publicVariable = "bar"; 
 var privateVariable = "foo"; 
 var privateMethod = function() {
  console.log(parameter);
  console.log(privateVariable);
  console.log(that.publicVariable);
  return "test";
 };

 this.publicVariable = privateMethod();

}

var x = new Parent("parent"); // parent foo bar
console.log(x.publicVariable); // test
console.log(x.privateVariable); // undefined
If private inner function is returned outside, its methods can be called from outside:
function Outer(){
 return new Inner();

 //private inner
 function Inner(){
  this.sayHello = function(){
   console.log("Hello World");
  }
 }
}

(new Outer()).sayHello(); // Hello World

11. Protected-Privileged Function&Field Pattern


Prototype methods can’t access private fields and everything attached to this is also “public”. We need a way to declare methods and fields so that they are accessed by any methods of object but not outside, which means protected access. For this purpose, following can be used:
var Car = (function(){
 var protectedStaticField = "foo";

 function Car(){
  this.publicField = "car";
 }

 Car.prototype.publicField2 = "bar";

 Car.prototype.testAccess = function(){
  console.log(protectedFunction());
  console.log(protectedStaticField);
 };

 return Car;
 
 function protectedFunction(){
  return "test"; 
 }

})();

var x = new Car();
x.testAccess(); // test foo
console.log(x.publicField); // car
console.log(x.publicField2); // bar
console.log(x.protectedStaticField); // undefined
console.log(x.protectedFunction()); // Error

12. Static Function&Field Pattern


Again, there is no a direct support for static members. Constructor functions are used for this purpose. Static members can’t be accessed with “this” keyword in its functions.
function Factory(){
}

Factory.staticFunction = function (){
return "static test";
};

Factory.staticField = "test";

Factory.prototype.test = function(){
console.log(this.staticField); // undefined
console.log(Factory.staticField); // test
console.log(Factory.staticFunction()); //static test
}

var x = new Factory();
x.test();

13. Singleton Pattern


If we need to use one instance of an object, this pattern can be used.
var Logger = {
 enabled:true,
 log: function(logText){
  if(!this.enabled)
   return;
  if(console && console.log)
   console.log(logText);
  else
   alert(logText);
 }
}
Or
function Logger(){
}
Logger.enabled = true;
Logger.log = function(logText){
 if(!Logger.enabled)
  return;
 if(console && console.log)
  console.log(logText);
 else
  alert(logText);
};

Logger.log("test"); // test
Logger.enabled = false; 
Logger.log("test"); // 

14. Prototypal Inheritance Pattern


Here is the inheritance implementation I used:
//base function
function Parent(){
 console.log("parent");
}
Parent.prototype = {
 parentData: "parent data",
 parentMethod: function(parameter){
  return "parent method"; 
 },
 overrideMethod: function(parameter){
  return parameter + " overrided parent method"; 
 }
}

function Child(){
//Its super constructor is not called by JS Engine we have to invoke it
 Parent.call(this);
 console.log("child");
}

//inheritance
Child.prototype = new Parent();
//nesne tipine bakınca doğru değeri bulabilmek için
Child.prototype.constructor = Child;

//lets add extented functions
Child.prototype.extensionMethod = function(){
 return "child's " + this.parentData;
};
One important limitation about “override inherited functions”; when calling parent’s method from child, you can’t access parent’s private instance fields. It behaves like a static method (Except “this” variable). To bypass that problem, you can pass instance fields as parameter when calling parent’s method. We can only access parent’s private fields in constructor method.
//override inherited functions
Child.prototype.overrideMethod = function(){
 //parent's method is called
 return "Invoking from child" + Parent.prototype.overrideMethod.call(this, " test");
}

var x = new Child();
console.log(x.extensionMethod()); //child's parent data
console.log(x.parentData); //parent data
console.log(x.parentMethod()); //parent method
console.log(x.overrideMethod()); //Invoking from child test overrided parent method


var x = new Child(); // parent child
console.log(x instanceof Parent); //true
console.log(x instanceof Child); //true
In child object’s constructor, we can access parent’s members:
function Parent(){
}
Parent.prototype.parentData = "test";

//inheritance
Child.prototype = new Parent ();

function Child(){
console.log(this.parentData); // test
}

var x = new Child();
console.log(x.parentData); // test

console.log(x instanceof Parent); // true
console.log(x instanceof Child); //true

15. Behavior (Decarator) Pattern


One object’s behavior is defined separately and it is activated in certain cases. In this way, we avoid to define new object types and its inheritance while increasing hierarchy level.
function InputTextComponent(){
}

InputTextComponent.prototype.addAutoSuggestBehaviour = function(){
 this.autoSuggestBehaviour = new AutoSuggest(this);
 console.log("AutoSuggest enabled");
};

InputTextComponent.prototype.removeAutoSuggestBehaviour = function(){
 this.autoSuggestBehaviour.unbind();
 delete this.autoSuggestBehaviour;
 console.log("AutoSuggest disabled");
};

function AutoSuggest(inputTextComp){
 this.bind(inputTextComp);
}

AutoSuggest.prototype.bind = function(inputTextComp){
 // attach keydown event etc.
};

AutoSuggest.prototype.unbind = function(){
 // detach keydown event etc.
};


var x = new InputTextComponent();
x.addAutoSuggestBehaviour(); // AutoSuggest enabled
x.removeAutoSuggestBehaviour(); // AutoSuggest disabled

16. Event (Observer or Publish-Subscribe) Pattern


We need custom events in JavaScript codes as well. Event programming has many advantages. Here is a typical event pattern:
function EventManager(){
}
var listeners = {};

EventManager.fireEvent = function(eventName, eventProperties) {
 if (!listeners[eventName]) 
  return;

 for (var i = 0; i < listeners[eventName].length; i++) {
  listeners[eventName][i](eventProperties);
 }
};

EventManager.addListener = function(eventName, callback) {
 if (!listeners[eventName]) 
  listeners[eventName] = [];
 listeners[eventName].push(callback);
};

EventManager.removeListener = function(eventName, callback) {
 if (!listeners[eventName]) 
  return;
 for (var i = 0; i < listeners[eventName].length; i++) {
  if (listeners[eventName][i] == callback) {
   delete listeners[eventName][i];
   return;
  }
 }
};

EventManager.addListener("myCustomEvent", function(props){
 console.log("Invoked event: " + props.test);
});

EventManager.fireEvent("myCustomEvent", {test:"testValue"}); // Invoked event: testValue

17. Logger Pattern


Logging is important in JavaScript codes especially for debugging. We can define a singleton logger and it can be switched on or off from one place:
var logger = new MyLogger(false);//false means not enabled

logger.log("debugging value x : " + x);

18. Method Chaining Pattern


Method chaining is one of the most used patterns JavaScript libraries. The key point for chaining that you return object reference every time you call a function.
function Car(owner){
 this.owner = owner;
}

Car.prototype.run = function(){
 console.log("Car is driven by Mr. " + this.owner);
 return this;
}

Car.prototype.stop = function(cause){
 console.log("Since "+ cause + ", car is stopped by Mr. " + this.owner);
 return this;
}

var x = new Car("Foo Lee");
x.run().stop("traffic jam").run(); // Car is driven by Mr. Foo Lee
        // Since traffic jam, car is stopped by Mr. Foo Lee
       // Car is driven by Mr. Foo Lee


19. Simple JavaScript Component Model (JSCOM) Pattern


We add new behaviors to the HTML elements and DOM objects with JavaScript. These enrichments can be combined in a component structure. Here is a commonly used pattern to bind JavaScript objects to DOM objects. In this way, we can collect behaviors and manage their lifecycles. 

1- Define JavaScript Component:

function InputTextNumberComponent(){
}

InputTextNumberComponent.prototype.bindDOMElement = function(domElement){
 domElement.onchange = function(){
  //just a format
  domElement.value = "-" + domElement.value + "-";
 };
domElement.jsComponent = this; //Expando property
 this.domElement = domElement;
};

InputTextNumberComponent.prototype.resetValue = function(){
 this.domElement.value = "";
};

2- Define CSS Entries to Correlate HTML Elements with JavaScript Components:

<style type="text/css">
.inputTextNumber { text-align:right; }
</style>

3- When page loaded (or DOM ready), detect the component and create-initialize it and bind to the DOM object (Note that for large web pages, this detection is expensive if many components are searched. For that reason some libraries requires to create components manually instead of auto-detection, by finding them with “id” attribute):

window.onload = function(){
 var inputTextNumbers = document.getElementsByClassName("inputTextNumber");
 for(var i = 0; i < inputTextNumbers.length; i++){
  var myComp = new InputTextNumberComponent();
  myComp.bindDOMElement(inputTextNumbers.item(i));
 }
};
HTML element should be as following:
<input type="text" class="inputTxtNumber" name="NumberField" size="10" value="Click me!" 

20. Module (AMD-Asynchronous Module Definition) Pattern


Module pattern provides “lazy loading”, “scoping” and “dependency management”. There are many approaches but I recommend “Every JavaScript file is a module” one. In module files, we define new types, import some types from other modules and export our public types.
Module.define('module1.js',['dependent_module1.js','dependent_module2.js',...],function(dependentMod1, dependentMod2) {

//TYPE DEFINITIONS

function ExportedType1(){
 // use of dependent module’s types
 var dependentType = new dependentMod1.DependentType1();
 ...
}

function ExportedType2(){
}
...

// EXPORTS
return { ExportedType1: ExportedType1, ExportedType2: ExportedType2,...};

});
To use a module (A parameter can be specified to invoke given function asynchronously or synchronously):
Module.use(["module1.js"], function(module1){
 console.log("Loaded file module1.js!");
var type1 = new module1.ExportedType1();
});

Resources:

http://www.amazon.com/JavaScript-Design-Patterns-Recipes-Problem-Solution/dp/159059908X
http://eloquentjavascript.net/
http://www.amazon.com/JavaScript-Good-Parts-Douglas-Crockford/dp/0596517742
http://libraryinstitute.wordpress.com/2010/12/01/loading-javascript-modules/
http://addyosmani.com/resources/essentialjsdesignpatterns/book/
http://www.ibm.com/developerworks/web/library/wa-jsframeworks/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章