Here are the most common JavaScript solutions that should be used in production code, everybody calls this list as JavaScript Design Patterns, but I think that it’s just good code’s rules. By the way, here they are:
immediate function pattern
purpose: to wrap some code in the context. Let us reduce polluting of the global context
(function () {
alert('Hello');
} ()); |
(function () {
alert('Hello');
} ());
purpose: the same solution, but with saving of results
var someVar = (function () {
return 'result';
} ()); |
var someVar = (function () {
return 'result';
} ());
purpose: here is the real world example, it allows to use $ symbol as a jQuery in one context and as a Prototype library in another context without polluting of the global context. This solution must be used with any external library.
(function ($) {
$(".menuOverlay").show();
} (jQuery || $)); |
(function ($) {
$(".menuOverlay").show();
} (jQuery || $));
note: Immediate function wrapped in parens, because:
- Without parens it would be interpreted as function declaration (not expression) which is syntax error
- Function expression can not start with word “function”
What is a Function Declaration?
function foo() {
return 'result';
} |
function foo() {
return 'result';
}
What is a Function Expression?
var bar = function () {
return 'result';
} |
var bar = function () {
return 'result';
}
immediate object initialization pattern
purpose: This pattern is mainly suitable for one-off tasks, and there’s no access to the object after the init() has completed. If you want to keep a reference to the object after it is done, you can easily achieve this by adding return this; at the end of init().
({
width: 100,
height: 200,
getArea: function () {
return this.width * this.height;
},
init: function () {
console.log(this.getArea());
//other init code
}
}).init(); |
({
width: 100,
height: 200,
getArea: function () {
return this.width * this.height;
},
init: function () {
console.log(this.getArea());
//other init code
}
}).init();
memoization pattern
purpose: the main goal is performance optimization. It’s real deal to save pre-calculated results. E.g. for Fibonacci numbers, juat implement solution with and without this pattern and you will see the difference.
function average(x, y) {
var key = x + '_' + y;
if (!average.cache.hasOwnProperty(key)) {
average.cache[key] = (x + y) / 2;
}
return average.cache[key];
}
average.cache = {};
average(10, 20); //calculate
average(10, 20); //take from cache
average(20, 10); //calculate |
function average(x, y) {
var key = x + '_' + y;
if (!average.cache.hasOwnProperty(key)) {
average.cache[key] = (x + y) / 2;
}
return average.cache[key];
}
average.cache = {};
average(10, 20); //calculate
average(10, 20); //take from cache
average(20, 10); //calculate
Loop closure pattern
purpose: to eliminate “cycle f**k ups”, I mean the situation when you use cycle iterator variable in closure and you expect 1, 2, 3 .. values in different functions, but you have always last value in all.
var arr = ['one', 'two', 'three'];
for (var i = 0, len = arr.length; i < len; i++) {
(function (i, d) {
//access index and data
console.log(i + ') ' + d);
} (i, arr[i]));
} |
var arr = ['one', 'two', 'three'];
for (var i = 0, len = arr.length; i < len; i++) {
(function (i, d) {
//access index and data
console.log(i + ') ' + d);
} (i, arr[i]));
}
The same code with jQuery
var arr = ['one', 'two', 'three'];
$.each(arr, function (i, d) {
console.log(i + ') ' + d);
}); |
var arr = ['one', 'two', 'three'];
$.each(arr, function (i, d) {
console.log(i + ') ' + d);
});
implementation is similar to
var forEach = function (d, f) {
for (var i = 0, l = d.length; i < l; i++) {
f(i, d[i]);
}
}; |
var forEach = function (d, f) {
for (var i = 0, l = d.length; i < l; i++) {
f(i, d[i]);
}
};
Private member
purpose: restrict access to private members
function Semaphore(start) {
var value = start; //private variable
//private method
function resetValue(){
value = 0;
}
return {
enter: function () {
value++;
},
leave: function () {
value--;
},
getValue: function(){
return value;
}
};
}
var sem = new Semaphore(0);
sem.enter();
sem.enter();
Assert(sem.getValue()==2);
sem.leave()
Assert(sem.getValue()==1);
Assert(!sem.hasOwnProperty('resetValue'));//could not access private method
Assert(!sem.hasOwnProperty('value')); //could not access private variable |
function Semaphore(start) {
var value = start; //private variable
//private method
function resetValue(){
value = 0;
}
return {
enter: function () {
value++;
},
leave: function () {
value--;
},
getValue: function(){
return value;
}
};
}
var sem = new Semaphore(0);
sem.enter();
sem.enter();
Assert(sem.getValue()==2);
sem.leave()
Assert(sem.getValue()==1);
Assert(!sem.hasOwnProperty('resetValue'));//could not access private method
Assert(!sem.hasOwnProperty('value')); //could not access private variable
Optional parameter pattern
purpose: add ability to specify optional parameters
When function has a lot of parameters
function Car(name, model, color, number, wheelsRadius, year, autoTransmission) {
//do something with all parameters
return 'result';
} |
function Car(name, model, color, number, wheelsRadius, year, autoTransmission) {
//do something with all parameters
return 'result';
}
This is bad code, because all parameters must be set when instantiating (even optional parameters)
e.g.
var myCar = new Car('Honda', 'civic', 'red', null, null, 2004, false); |
var myCar = new Car('Honda', 'civic', 'red', null, null, 2004, false);
also the order of the parameters is fixed and it’s hella hard to add one more parameter, when this is boilerplate code
So, the decision is to convert list of parameters to one object…
function Car(settings) {
var defaultSettings = {
name: 'Unknown',
model: null,
autoTransmission: false,
wheelsRadius: 16
};
this.settings = $.extend({}, defaultSettings, settings);
return this.settings;
}
var myCar = new Car({ name: 'Honda', model: 'civic', color: 'red', year: '2004', autoTransmission: true }); |
function Car(settings) {
var defaultSettings = {
name: 'Unknown',
model: null,
autoTransmission: false,
wheelsRadius: 16
};
this.settings = $.extend({}, defaultSettings, settings);
return this.settings;
}
var myCar = new Car({ name: 'Honda', model: 'civic', color: 'red', year: '2004', autoTransmission: true });
Note: if function has more then 3 parameters it must be converted to the function with one object-parameter.
Namespace pattern
purpose: reduce pollution of global context and simplify code organisation.
Here is Bad practice (pollute global scope):
function Init() { };
function Run() { };
var state = 1;
var module1 = {};
module1.data = [1, 2, 3];
var module2 = {}; |
function Init() { };
function Run() { };
var state = 1;
var module1 = {};
module1.data = [1, 2, 3];
var module2 = {};
Here is Better practice, but not good enough:
var MYAPP = {};//only one global object;
MYAPP.Init = function () { };
MYAPP.Run = function () { };
MYAPP.state = 1;
MYAPP.module1 = {};
MYAPP.module1.data = [1, 2, 3];
MYAPP.module2 = {}; |
var MYAPP = {};//only one global object;
MYAPP.Init = function () { };
MYAPP.Run = function () { };
MYAPP.state = 1;
MYAPP.module1 = {};
MYAPP.module1.data = [1, 2, 3];
MYAPP.module2 = {};
Why not good enough? Because if you have a lot of modules, you will do something like:
if (typeof MYAPP === "undefined") {var MYAPP = {};}
if (typeof MYAPP.modules === "undefined") {var MYAPP.modules = {};} |
if (typeof MYAPP === "undefined") {var MYAPP = {};}
if (typeof MYAPP.modules === "undefined") {var MYAPP.modules = {};}
Here is the Namespace pattern (read it carefully, it’s pretty interesting):
var MYAPP = MYAPP || {};
MYAPP.namespace = function (nameSpace) {
var parts = nameSpace.split("."),
parent = MYAPP,
i;
if (parts[0] === "MYAPP") { // strip redundant leading global
parts = parts.slice(1);
}
for (i = 0; i < parts.length; i++) {
// create a property if it doesn't exist
if (typeof parent[parts[i]] === "undefined") {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
}; |
var MYAPP = MYAPP || {};
MYAPP.namespace = function (nameSpace) {
var parts = nameSpace.split("."),
parent = MYAPP,
i;
if (parts[0] === "MYAPP") { // strip redundant leading global
parts = parts.slice(1);
}
for (i = 0; i < parts.length; i++) {
// create a property if it doesn't exist
if (typeof parent[parts[i]] === "undefined") {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
};
To create new (or get already existing) namespace you need to write:
var moduleCar = MYAPP.namespace("MYAPP.modules.moduleCar"); |
var moduleCar = MYAPP.namespace("MYAPP.modules.moduleCar");
Module pattern
purpose: Javascript doesn’t have special syntax for packages, but with help of “namespace” and “immediate function” patterns we can achieve this:
MYAPP.namespace("MYAPP.utilities.stack");
MYAPP.utilities.stack = (function () {
var stack = [];
function push(value) {
stack.push(value);
}
function pop() {
return stack.pop();
}
function somePrivateMethod() {
stack = [];
}
return { // public API
push: push,
pop: pop
};
} ()); |
MYAPP.namespace("MYAPP.utilities.stack");
MYAPP.utilities.stack = (function () {
var stack = [];
function push(value) {
stack.push(value);
}
function pop() {
return stack.pop();
}
function somePrivateMethod() {
stack = [];
}
return { // public API
push: push,
pop: pop
};
} ());
Chaining pattern
purpose: add ability to use similar cool stuff as it possible with jQuery (I mean this chaining – $(‘.container’).html(markup).show() …)
var calculator = (function () {
var value;
return {
init: function (startValue) {
value = startValue;
return this;
},
add: function (val) {
value += val;
return this;
},
mul: function (val) {
value *= val;
return this;
},
result: function () {
return value;
}
};
} ());
console.log(calculator.init(2).add(2).mul(2).result());//8 |
var calculator = (function () {
var value;
return {
init: function (startValue) {
value = startValue;
return this;
},
add: function (val) {
value += val;
return this;
},
mul: function (val) {
value *= val;
return this;
},
result: function () {
return value;
}
};
} ());
console.log(calculator.init(2).add(2).mul(2).result());//8
P.S. It’s pretty obvious, but you can use different combination of these patterns to achieve your goal.