Onderhoudbaar Javascript
Javascript schrijven is één ding, onderhoudbaar Javascript schrijven is iets anders. In een tijd waarin allerlei nieuwe technieken en third party scripts worden gebruikt die een behoorlijke dosis Javascript vereisen, is het bijzonder nuttig om je code niet alleen voor jezelf duidelijk op te schrijven, maar ook voor je collega’s onderhoudbaar te houden.
Javascript basisprincipes
Er zijn eigenlijk twee basisprincipes binnen Javascript die de taal zowel flexibel en krachtig als ondoorzichtig en foutgevoelig maken. De eerste:
Alles is een object
Nou ja, bijna alles. Er zijn de literals Number, String en Boolean, maar voor deze literals is een soort object wrapper beschikbaar en aangezien Javascript loosely typed is, ben je met objecten bezig voordat je het weet. Test maar:
alert((2).toExponential());alert('foo'.toUpperCase());alert(true.constructor);alert(typeof null);
Buiten objecten is er niets. Javascript kent geen classes, packages etc. Arrays zijn objecten met numerieke keys. Functies zijn ook objecten. Haakjes (()) zijn een operator op een functie-object, ze voeren de functie uit. Je kan deze dus ook zetten achter iets wat een functie retourneert.
function foo(arg) {
return arg||foo;
}
alert(foo()()()()()('test'));
Alle objecten zijn onbeperkt uitbreidbaar
Aan alle objecten zijn onbeperkt properties (en methods, maar dat zijn ook properties) toe te voegen. Dit geldt voor je eigen objecten, maar ook voor andermans objecten.
Scoping
Gedeclareerde variabelen zijn zichtbaar binnen de functie waarbinnen ze gedeclareerd zijn, verder zijn ze zichtbaar in geneste functies, dat wil zeggen, functies die gedeclareerd worden binnen dezelfde scope als de variabele. Niet gedeclareerde variabelen die zomaar worden gebruikt, worden automatisch gedeclareerd binnen het Global Object. In browsers is dit het window object. Aangezien functies ook gewoon objecten zijn geldt dat dus ook voor functiedeclaraties en -expressies.
Tips
Breidt nooit andermans objecten uit
Dit geldt voornamelijk voor de globale objecten, zoals Object en Array. Het is op dit moment enorm populair aan het worden om eigen functionaliteit aan deze objecten te hangen (zoals een Array.each method), maar doe dat alsjeblieft niet! Nu al zijn er tussen verschillende libraries verschillen in de implementatie ervan. Bovendien, stel dat Javascript uitgebreid wordt met een each method voor Array, die net een tikkie anders is dan wat jij had. Overerf gewoon een nieuw object (je mag class lezen) van Array en breidt die uit met je eigen methods.
Gebruik je eigen scope
Noem het private scope, package of namespace, het is een prive gebied waarin alleen jij de naamgeving bepaald. Geen functies en variabelen dus in de globale scope. Als het goed is merk je nu op dat dit onmogelijk is in combinatie met het bovenstaande. Als ik niets mag uitbreiden en ook niets in de globale scope mag stoppen, waar laat ik dan m’n code? Maak één object aan in de globale scope, zorg ervoor dat die een unieke naam heeft (een reversed domain name vind ik persoonlijk een goed idee) en stop daarin alles wat je maakt. Dit lijkt behoorlijk veel op het packaging concept in Java. Voorbeeld:
if (!com) {var com = {};} //niet overschrijven
if (!com.finalist) {com.finalist = {};}
/*
dit is onze eigen prive scope,
waarin we alles mogen doen
wat we zelf willen.
*/
//een voorbeeld package
com.finalist.voorbeeld = function() {
//fill the object
//return the object
return {};
}();
Kijk eens met Firebug[1] wat verschillende Wordpress libraries allemaal in het window object gooien (”DOM” tab). Dit is een mooi voorbeeld van Global Pollution.
Gebruik geen syntax sugar of overerving tools
Javascript heeft z’n eigen syntax. Soms is die wat vreemd, maar het is een syntax. Probeer niet andere talen te emuleren omdat dat voor jou net wat makkelijker werkt, het levert vraagtekens op bij iemand die je code bekijkt. Het is bijvoorbeeld heel goed mogelijk om een (global) CLASS functie te maken die het volgende toelaat:
var BClass = CLASS({
EXTENDS: 'AClass',
STATIC: {
total: 0
},
PRIVATE: {
Foo: function(){},
Bar: true
}
});
Dit is misschien voor sommige mensen erg prettig leesbaar, maar het is niet echt Javascript meer te noemen. Gebruik gewoon de standaard Javascript methoden en verpak het niet in iets wat voor jou misschien makkelijker is, maar voor een ander compleet onbegrijpbaar. Gebruik de taal, het is niet zo ingewikkeld dat er abstracties nodig zijn voor object inheritance.
Gebruik een duidelijke naamgeving
Een functie s noemen scheelt natuurlijk veel typewerk als je het veel gebruikt. Maar het is volkomen onduidelijk wat die functie nu eigenlijk doet. Dit is zeker vervelend als een collega later aan de slag gaat met jouw code en overal de functie s tegen komt. Het is geen ramp als een functienaam wat langer is, je hebt een ctrl-v waar je hem onder kunt stoppen als je echt RSI verschijnselen krijgt. Maak je niet druk over die paar extra bytes die naar de client moeten. Javascript libraries kunnen prima worden gecached en ge-gzipt worden verstuurd.
Gebruik creator functions voor classes
Hier hebben we al een voorbeeld van gezien, namelijk bij het maken van een package. Het idee van een creator function is dat je alles wat je nodig hebt om een object te maken, binnen een functie doet. Die functie laat je vervolgens het gebouwde object retourneren. Omdat deze functie maar één keer gebruikt wordt, kan het zelfs een anonieme functie zijn die we meteen uitvoeren. Om maar eens een voorbeeldklasse op te nemen in bovenstaande package:
com.finalist.voorbeeld = function() {
var AClass = function() {
var _o = function(){};
return _o;
}();
return {
AClass: AClass
};
}();
Hier creëren we de klasse (het object) AClass. Binnen de scope van de package (het object) voorbeeld is deze klasse beschikbaar voor alle andere objecten. Als we ook buiten de package gebruik willen maken van deze klasse, moet deze worden gepubliceerd, dat kan door hem te retourneren in het package object. Merk op dat dit eventueel onder een andere naam kan gebeuren. We maken in dit voorbeeld op twee plaatsen gebruik van creator functions, namelijk voor het creeren van de package en voor het creëren van de klasse. Naast het beheersen van scope en visibility heeft deze methode nog een extra voordeel dat je code duidelijker wordt door indenting en keywords. Dat had ook wel op een andere manier gekund, maar hier krijg je het als bonus erbij.
Gebruik geen anonieme functies als ze veel gebruikt worden
Tegenwoordig zijn er Javascript profilers in omloop, Firebug[1] is daar een voorbeeld van. Stel je schrijft de volgende code en iemand die je code wil gaan aanpassen wil weten wat er gebeurt (druk op de “profile” knop in Firebug, druk op de “Doe” knop, wacht even en druk weer op de “profile” knop).
setTimeout(function() {
alert('foo');
},500);
Gedaan? Je hebt nu dus geen idee wat er is gebeurd als je de boel niet zelf gemaakt hebt (of misschien als je het wel gemaakt hebt, maar al een hele tijd geleden). Handiger is dus:
setTimeout(function alertFoo() {
alert('foo');
},500);
Uiteraard met een beschrijvender functienaam dan “alertFoo”.
Een uitgebreid voorbeeld
Hier een uitgebreid voorbeeld waar ik allerlei dingen gebruik die je kan aanduiden met termen als private, public, static, class, package etc. In Javascript komt het allemaal neer op objecten en scoping.
if (!com) {var com = {};} //niet overschrijven
if (!com.finalist) {com.finalist = {};}
//een voorbeeld package
com.finalist.voorbeeld = function() {
//package private
var packagePrivateProperty = true;
//public class
var AClass = function() {
var privateProperty = 'bar';
AClass.staticProperty = 0;
var _o = function() {
//public property
this.publicProperty = 'foo';
};
return _o;
}();
//private class
var BClass = function() {
var _o = function() {};
//overerf van AClass
_o.prototype = new AClass();
return _o;
}();
return {
AClass: AClass
};
}();
Als laatste
Javascript is dus bij uitstek een object georienteerde taal. De gebruikte syntax en methodiek is wat anders dan in de meeste talen, maar daarom niet minder krachtig en flexibel. Aan de andere kant is Javascript een taal waarmee enorm veel spaghetti code wordt geschreven. Probeer knip-en-plak code zoveel mogelijk te vermijden en denk vooral ook aan de onderhoudbaarheid van het geheel. Er wordt meer code beheerd dan geschreven tegenwoordig.
Voetnoten
- Firebug is een extensie voor Firefox en bevat onder andere een DOM inspector, Javascript profiler, CSS en HTML editor en een network monitor.


