Object orientating and prototypes
We covered getting the argument from the query string and the
parseQuery
function in Passing JavaScript arguments via the src attribute. While the
code is straightforward it could get messy when used in a larger script
or if we decide we want it to carry more utility. We want to turn it
into an object.
- object oriented programming
- a way of abstracting complex behavior and data into compact classes or packages. Unlike a regular datum, the object knows its scope/class, its data, and its functions, available methods, and it offers simple ways to interact with it all. Object oriented code is generally inheritable, meaning an object can be reused with modifications by a child class. Built-in reusable extensibility makes it an exceptionally powerful programming idiom.
So far we’ve been developing functional techniques. That is, all our scripts and services have relied directly upon functions. If we write, for example:
function sum () { var sum = 0; for ( var i = 0; i < arguments.length; i++ ) sum += arguments[i]; return sum; }
That’s a function. It isn’t a part of an object or a class (though it
does rely tacitly on Number()
objects and explicitly on
the arguments
object). It’s argument is a number list.
Summing a number sequence
var sum_of_num = sum( 1,2,3,5,8,13 ); document.write( sum_of_num ); function sum () { var sum = 0; for ( var i = 0; i < arguments.length; i++ ) sum += arguments[i]; return sum; }
The functional sum
If instead we write:
Array.prototype.sum = function () { var sum = 0; for ( var i = 0; i < this.length; i++ ) sum += this[i]; return sum; }
We’ve written a method, not a function, that any object in the
Array()
class can call on itself.
Summing a number sequence in an array
var num = new Array( 1,2,3,5,8,13 ); document.write( num.sum() );
The object oriented method sum
You can see they both work fine. You can see they are both easy to understand and roughly the same level of coding difficulty.
Why choose one over the other?
Though it’s rarely used that way in the wild, JavaScript is an object
oriented language which supports inheritance and custom classes.
Purists will argue what this really means but all the data types in
JavaScript are objects as well as the DOM, obviously. A few naked
global function calls like escape()
aside, it’s all
objects. Even escape()
and friends are transparent
methods of the contextual Global()
class.
We’re going to go object oriented because we need our code to be reusable and because we want get the complexity out of the way. Yes, the complexity still exists, but with OO it’s beneath a nice API, the hackers’ version of the UI. It’s not a tangle to reuse or a stumbling block to expanding functionality.
This isn’t necessarily the right™ way to do it. It is natural, though. JavaScript is an object oriented language already. And there are compelling reasons to move that way.
Imagine, for example, if you had 30 functions in a script to do date
work instead of a Date()
object that had those methods
built-in. And you had to copy those functions—weighing in at about
1,000 or more lines of code—into every script where you wanted to use
them. Just say no.
How to write an object class
Basically any function in JavaScript can return an object if it is
used with the new
keyword in front of it. It really could
not be easier. Not all functions would return a sensible object, of
course. They need to be written to that end.
When you do call a function, now an object constructor, with
new
, like new Date()
, it gets a special
property: this
. this
is a built-in
keyword for the object that’s created.
A simple object class/constructor
function Guitar ( model, make, price ) { this.model = model || '?'; this.make = make || '?'; this.price = price || '?'; }
It doesn’t get much simpler or elegant than that in any programming language.
How the class is used
var righteousAxe = new Guitar( "'59 Les Paul", "Gibson" ); document.write( 'I love this ' + righteousAxe.model + '!' );
The output
The capitalized “Guitar” is a style convention for constructors in
JavaScript but has no programmatic meaning. It could be called
royalFizbin()
or ___URKLE_1_2_3_()
. We
choose the shortest sensible name and stick with convention so we
don’t confuse other developers, or ourselves when we return to the
code a year later and wonder how we got anything done being that drunk
all the time.
Method, man
You can already see how easy it is to wrap up data in an object. We
can have as many attributes as we want and they can be added on the
fly, just like with regular Object()
s. The next step to
the power of classes is built-in functions the objects can call on
themselves: methods.
A function installed into an object, via its prototype
property, is a method. It has no name outside the object. It is only
known to denizens of the class it’s installed in.
Methods: setPrice() & showPrice()
Guitar.prototype.setPrice = function ( price ) { this.price = price; } Guitar.prototype.showPrice = function () { document.write( this.price ); }
setPrice() & showPrice() in action
var righteousAxe = new Guitar( "'59 Les Paul", "Gibson" ); document.write( 'I love this ' + righteousAxe.model + '!' ); righteousAxe.setPrice('$62,000'); righteousAxe.showPrice(); // Oh, well... There's always Epiphone.
Method() != Function()
Remember that a method belongs to a class; to any object created through that class. It’s not the same thing as a method. If we do this now:
Wrong-o was his name-o
var sadTruth = new Guitar( "Sears catalog special", "Cort" ); document.write( 'I can afford this ' + sadTruth.model + '.<br />' ); sadTruth.setPrice('$119'); document.write( "It's only " + showPrice() );
We’ll get a fatal error. There is no such function as
showPrice()
. It’s a method that can only be invoked
through Guitar()
objects.
Class data
One more thing that will come handy often. Each object created with
new Whatever()
is often referred to as an instance of the
class. The class itself can also have attributes which are available
within the class to all instances.
Guitar.stringCount = 6; Guitar.tuning = [ 'E','A','D','G','B','E' ];
Then stringCount
and tuning
1 can be used throughout the Guitar
class and maintain
themselves, their state, across different objects, or instances. Class
data isn’t visible without the Guitar
prefix so you can’t
have namespace collisions with it; though it can be overridden or
overwritten by another class extension which is easy to accomplish but
difficult to do accidentally.
Don’t start from scratch if you can help it
The best part about classes is that they can be inherited and extended
via the prototype
property of all objects. We don’t even
need to make our own. We can built on others’ or on the built-ins. For
example, we can extend Array()
objects to be able to
join themselves into a string with a serial comma.
Serial comma join for Array()
Array.prototype.serial = function () { switch( this.length ) { case 0 : return false; case 1 : return this[0].toString(); case 2 : return this.join(' and '); default: return this.slice(0,-1).join(', ') + ', and ' + this[ this.length - 1 ]; } }
Array.serial() test script
var things = new Array( 'Caracal', 'Margay', 'Serval', 'Ocelot' ); for ( var i = 1; i <= 5; i++ ) { document.writeln( things.serial() + ".<br />" ); things.shift(); // extinction... }
The rare cats you’ll have a chance to see in your lifetime
Where to start for our services
We are certain we like the ability to
get query string arguments along with our client scripts. So
that’s an excellent place to begin. The code that does it isn’t so
simple that we’d want it pasted around everywhere drifting apart and
adapting to its various niches. Our first class will build an
Args()
object that has automatic (Greek for self)
access to the parsed query string parameters.
When we’re done we’ll be able to use something like this in a client call:
<script src="http://elektrum.org/sandwich.js?cheese=brie;bread=honey+wheat" type="text/javascript"> </script>
And get at it on the service side as easily as this:
var args = new Args(); document.write("Cheese: " + args.cheese); document.write("<br />"); document.write("Bread: " + args.bread);
Once it’s out in its own class we’ll be able to use new
Args()
objects to our heart’s delight in as many different
services as our fevered brains can cook up. Here we go: Args().