Saturday, November 12, 2011

Few JavaScript Patterns

Just to be clear and once again, JavaScript can emulate:
  • classes
  • public and public static methods or properties
  • private and private static methods or properties
  • public and private constants
  • protected methods
  • ... you name it ...

// duck typing ( maybe all you need )
var me = {name: "WebReflection"};

// basic class
function Person() {}
Person.prototype.getName = function () {
return this.name;
};
Person.prototype.setName = function (name) {
this.name = name;
};

// module pattern + private properties / methods
function Person(_name) {
function _getName() {
return _name;
}
return {
getName: function () {
// redundant, example only
return _getName();
},
setName: function (name) {
_name = name;
}
};
}

// private shared methods via this
var Person = (function () {
function Person() {}
function _getName () {
return this.name;
}
function _setName (name) {
this.name = name;
}
Person.prototype.getName = function () {
return _getName.call(this);
};
Person.prototype.setName = function (name) {
_setName.call(this, name);
};
return Person;
}());

// private shared method Python style
var Person = (function () {
function Person() {}
function _getName (self) {
return self.name;
}
function _setName (self, name) {
self.name = name;
}
Person.prototype.getName = function () {
return _getName(this);
};
Person.prototype.setName = function (name) {
_setName(this, name);
};
return Person;
}());

// public static
function Person() {
Person.TOTAL++;
}
Person.TOTAL = 0;

// private static / constant
var Person = (function () {
var TOTAL = 0;
return function Person() {
TOTAL++;
};
}());

// public constant
function Person() {}
Object.defineProperty(Person, "RACE", {
writable: false, // default
configurable: false,// default
enumerable: true,
value: "HUMAN"
});

// public inherited constant
function Person() {}
Object.defineProperty(Person.prototype, "RACE", {
writable: false, // default
configurable: false,// default
enumerable: true,
value: "HUMAN"
});

// protected method
function Person() {}
Person.prototype.getName = function () {
return this instanceof Person ?
this.name :
throw "protected method violation"
;
};
Person.prototype.setName = function (name) {
this instanceof Person ?
this.name = name :
throw "protected method violation"
;
};

// generic protected methods
Function.prototype.protectedVia = function (Class) {
var method = this;
Class || (Class = Object);
return function () {
if (this instanceof Class) {
return method.apply(this, arguments);
}
throw "protected method violation on " + (
Class.name || Class
);
};
};
function Person() {}
Person.prototype.getName = function () {
return this.name;
}.protectedVia(Person);
Person.prototype.setName = function (name) {
this.name = name
}.protectedVia(Person);

// private shared variables
function Person() {}
Person.prototype.getName = function () {
return this.name;
};
(function (PersonPrototype) {
var changes = 0;
PersonPrototype.setName = function (name) {
changes++;
this.name = name;
};
PersonPrototype.howManyChangedName = function () {
return changes;
};
}(Person.prototype));

// getters / setters on public property
var person = Object.defineProperty({}, "name", {
get: function () {
return this._name;
},
set: function (_name) {
this._name = _name;
}
});

// getters setters on public property via prototype
function Person() {}
Object.defineProperty(Person.prototype, "name", {
get: function () {
return this._name;
},
set: function (_name) {
this._name = _name;
}
});

// getters / setters on private property
var person = Object.defineProperty({}, "name", (function () {
var _name;
return {
get: function () {
return _name;
},
set: function (name) {
_name = name;
}
};
}()));

// singleton
var me = {name: "WebReflection"};

// singleton via anonymous __proto__
// plus private properties
var me = new function () {
var _name;
this.getName = function () {
return _name;
};
this.setName = function (name) {
_name = name;
};
};

// generic singleton cross browser
Function.prototype.singleton = (function () {
function anonymous(){};
function create(Class, args) {
anonymous.prototype = Class.prototype;
Class.apply(
Class.__singleton__ = new anonymous,
args
);
return Class.__singleton__;
}
return function singleton() {
return this.__singleton__ || create(this, arguments);
};
}());

// generic singleton ES5
Function.prototype.singleton = (function () {
function create(Class, args) {
Class.apply(
Class.__singleton__ = Object.create(
Class.prototype
), args
);
return Class.__singleton__;
}
return function singleton() {
returh this.__singleton__ || create(this, arguments);
};
}());

// per function private singleton
var Person = (function () {
var instance;
return function Person () {
return instance || (instance = this);
// or ... ot be more than sure ...
return instance || (
(instance = true) && // avoid infinite recursion
(instance = new Person)
);
};
}());

// generic factory
Function.prototype.factory = (function () {
function anonymous() {};
return function factory() {
anonymous.prototype = this.prototype;
var instance = new anonymous;
this.apply(instance, arguments);
return instance;
};
}());

// generic factory ES5 + ruby style
Function.prototype.new = function factory() {
var instance = Object.create(this.prototype);
this.apply(instance, arguments);
return instance;
};

10 comments:

Aadaam said...

OK, but, just by looking at it, don't you have the urge that you need another language to generate this boilerplate from?

Classical inheritance shouldn't be emulated: it should be deeply embedded into the language, writeable with the fewest characters possible.

In real life, we don't think in prototypes: we don't think that the Berlin-Paris route is like the Berlin-London route except it goes elsewhere: we think there's a generic category of "routes" of which both are members, and we expect we can do the same things about them.

However, it's so easy to fail to differentiate type systems and class hierarchies - look at Kevlin Henney's presentation here: http://www.infoq.com/presentations/It-Is-Possible-to-Do-OOP-in-Java

It's nice to think in DCI-style architecture - eg. an array of POIs is one time a route, one time a list of search results, depending on context - , in real life however, a given list of POIs will be used for only one of those purposes.

A short,easy-to-write, easy-to-read, hackless and fast class system is truly missing from javascript, no matter how we try to hide it behind idioms.

Keith H said...

I'm gonna end up working with JavaScript more so thanks for your post!

JavaScript is very bendable, thats good for some but the syntax is like the Wild Wild West...The langauge lets code bandits run wild making it too easy for code to become unreadable when it lets developers write anyway they want while not including good debugging features.

I rather work with real Classes, avoiding all the hoop jumping to emulate it. Classes make working with others as a team more efficient. They help keep the code in control...emulating theses features are worth it but still a major pain to set up most likely making more issues to debug.

Anonymous said...

@Aadaam: agree. "Concentrate on good parts" they say, - we should always remember though that JS has many "not so good" parts and also "simply missing" parts.

Andre said...

@Keith H: Do take a look at AMD (Asynchronous module definition). It allows for easy code separation and asynchronous loading and automatic dependencies handling. It seems like everyone is going that route now. Even jQuery 1.7 added AMD support. Extremely convenient as the only file that you would need to load is AMD loader (like require.js or curl.js ).

Mark Sandman said...

Can I just ask... why have you used "Function" in "Function.prototype.protectedVia"? Is Function meant to be started with the uppercase (as in Function() constructor) rather than function?

I'm a bit of a newbi to OO JavaScript.

Andrea Giammarchi said...

Function.prototype is the one inherited by all functions so no error there, it makes you able to do whateverFuncYouWant.protectedVia(whateverClassYouWant);

AutoSponge said...

@Aadaam: try reading any philosophy after the Greeks. Prototypes are a more advanced and natural system of modeling inheritance. The fact that the creators of so many modern languages missed it does not mean we have to go without.

5pisici said...

wow, nice collection of js patterns. I'm using the "module pattern + private properties / methods" in my every day JS dev and it's working great for me.
I will check the other patterns as well, thank you.

Andrea Giammarchi said...

I should probably write another post explaining advantages and disadvantages of each pattern ... as example module + privates can be useful but it cannot be the way all the time because it uses extra RAM, extra CPU cycles, extra redundant operations, etc etc ... if you have to create million Persons, the classic prototype approach is way better than module pattern, as example ;-)

Kowser said...

Just to say thank you. Nice article.