Easy object-oriented Javascript the Python way
Summary
Javascript is not an opinionated language. At it’s heart it is a hash map. You can layer pretty much any idiom you want on top of it. I’d like to make it look like Python, and it’s pretty easy to do. They both are dynamically typed, have functions as first class objects, and can treat most types as hash maps.
Let’s assume code for a whimsical Moose Observation Project, and translate it from Python to Javascript.
The Python
Here is mop.py:
ANSWER = 42
class Moose:
BULL = 1
COW = 2
def __init__(self, sex):
self.legs = 4
self.sex = sex
def speak(self):
if self.sex == Moose.BULL:
print "a heavy grunt-like noise that can be heard up to half a kilometer away"
elif self.sex == Moose.COW:
print "a wail-like bawl"
else:
print "Sexless moose say "+ self._default_sound()
def _default_sound(self):
return str(ANSWER)
The Javascript
And here is mop.js:
if (typeof MOP == "undefined" || !MOP) {
var MOP = {};
}
MOP.ANSWER = 42;
MOP.Moose = function(sex) {
// In case we forget the 'new' keyword
if ( !(this instanceof MOP.Moose) ) {
return new MOP.Moose(sex);
}
this.legs = 4;
this.sex = sex
}
MOP.Moose.BULL = 1;
MOP.Moose.COW = 2;
MOP.Moose.prototype = {
speak:
function() {
if (this.sex == MOP.Moose.BULL) {
console.log("a heavy grunt-like noise that can be heard up to half a kilometer away");
}
else if (this.sex == MOP.Moose.COW) {
console.log("a wail-like bawl");
}
else {
console.log("Sexless moose say "+ this._default_sound());
}
},
_default_sound:
function() {
return MOP.ANSWER;
}
}
Using it
In Javascript:
var bennyTheMoose = new MOP.Moose(MOP.Moose.BULL);
bennyTheMoose.speak();
The details
Let’s look at the interesting parts in more detail. Note that below when I say property I mean attribute or method.
The module is just a namespace
Because you don’t know what other scripts might be included on a page that uses your script, you need to use the smallest amount possible of the global namespace. Create an object, and put everything else you define in that object. Yahoo, for example, puts everything they write in the YAHOO
object / namespace. Here our namespace is called MOP
.
new: The class is a function which returns an object
The two parts of Javascript that make object oriented programming possible are the new
operator applied to a function, and the prototype
attribute of that function.
When a function call is preceded by the new
operator, it gets given a new object called this
, and it returns it. The Moose
function is your constructor It is good practice to define your object’s data in there, same as it is in Python. It returns your new instance.
If you forget the new
operator, you get no warning. The function is called as a function instead of as a constructor. this
is bound to the global window
object, and the function returns undefined
. Your call is perfectly valid, yet it does something very different to what you wanted. A lot of Javascript is like that! To prevent this we added a check at the top of the constructor to use new
if you forget it.
prototype: Instances can have methods – but ignore that
A significant difference between Javascript and most other languages is that in Javascript object instances can have properties. In most other languages, only the class has properties, and all instances share them. We’re trying to be Pythonic, so let’s not use this feature, but knowing about it helps to understand the prototype
attribute.
When you call a method on an instance, if that method is not found, it will try it’s prototype
attribute. The prototype
attribute is shared amongst all instances constructed with the same constructor (because prototype
is an attribute of the constructor function). If we put all our methods on the prototype, we get the same behavior as in a more traditional object-oriented language.
Object literals keep things tidy
I’m using an object literal to list the methods of the Moose
class. Here is the equivalent code without using an object literal:
MOP.Moose.prototype.speak =
function() {
if (this.sex == MOP.Moose.BULL) {
console.log("a heavy grunt-like noise that can be heard up to half a kilometer away");
}
else if (this.sex == MOP.Moose.COW) {
console.log("a wail-like bawl");
}
else {
console.log("Sexless moose say "+ this._default_sound());
}
};
MOP.Moose.prototype._default_sound =
function() {
return MOP.ANSWER;
};
I think an object literal wraps the functions up more neatly and saves some typing.
Private methods
Javascript, like Python, doesn’t give you anything built in to declare an access level – everything is public. Using closures, you can have private methods in Javascript – examples here and item 6 here – but that’s not how Python does it.
In Python the convention is to prefix private methods with a single underscore. They can still be called, but you are warning the user that they are now on their own, all bets are off. Let’s simply carry that convention over to Javascript: _default_sound
is a private method.
Conclusion
- Put everything in a namespace.
- Your class is a function.
- That function is your constructor. You declare your attributes in that constructor.
- You call that function with the
new
operator. - The methods of your class go on the
prototype
attribute of that function. - Use the underscore-prefix convention to mark a private method.
Javascript and Python have a lot in common, and applying some of Python’s Zen to Javascript helps me impose some order on the chimera that is Javascript. I hope it helps you too.