Objecten in javascript

Een tutorial over het gebruik van objecten in javascript, door Peter Nederlof. 2 maart 2004.


Objecten

Dit onderdeel beschrijft de basis van Objecten in javascript en het opzetten van een eigen object structuur. Daarnaast is er een klein aantal zijsprongen naar gerelateerde zaken.

Vooraf

Deze tutorial gaat er vanuit dat je bekend bent met de basis van javascript. Dat wil zeggen; Het gebruik en de werking van variabelen (zoals het verschil tussen globale en lokale variabelen etc) en methoden. Is dat niet het geval dan is het aan te raden eerst een aantal algemenere javascript tutorials te lezen.

Het is ook mogelijk dat je soms dingen tegenkomt die je al lang weet; Sla deze dan gerust over.

Overzicht

De tutorial bestaat uit de volgende onderdelen:

Objecten

Basis

Een object is het beste te vergelijken met een voorwerp of ding. Dit ding heeft eigenschappen die specifiek van toepassing zijn op zichzelf. Zo heeft een auto als eigenschap het aantal stoelen. Daarnaast heeft een ding acties die deze specifieke eigenschappen kunnen aanpassen. Het starten van de genoemde auto is hier een voorbeeld van.

Deze eigenschappen en acties van objecten worden altijd aangeroepen met een punt

auto.stoelen;  // bijvoorbeeld 4
auto.start();

Dit is waarschijnlijk al bekend door het gebruik van een of meer van de volgnde functies waarin document het object is en de functies achter de punt de object specifieke acties.

document.getElementById('id');
document.write('hello world');

Als er in een object een ander object zit waarvan een eigenschap opgevraagd moet worden, of als je een functie ervan wil uitvoeren, zul je in principe alle voorgaande objecten moeten noemen met een punt ertussen. Een extreem voorbeeld is:

parent.frames.main.document.body.offsetWidth;

Dit zou de breedte zijn van de body in een frame wat "main" heet, opgevraagd vanuit een ander frame.

Eigen objecten

Waarom ?

Naast het gebruiken van de standaard objecten binnen een pagina kan je ook eigen objecten aanmaken in javascript. Natuurlijk moet dit geen doel op zich zijn, maar objecten bieden een groot aantal voordelen over het niet gebruiken ervan. Het grootste voordeel is dat je niet 1 lineair script schrijft, maar een stuk code dat je een oneindig aantal keren kan gebruiken binnen dezelfde pagina zonder de code hiervoor te hoeven aanpassen. Zo kan je bijvoorbeeld 1 dhtml scrollbar scripten en die binnen dezelfde pagina 4 keer gebruiken. Enkele andere belangrijke voordelen zijn:


Het idee

Het kernpunt, en dat is ook het abstracte idee wat misschien het moeilijkste is van objecten, is dat je niet echt een normaal uitvoerbaar stuk code schrijft, maar dat je eigenlijk de blauwdruk van je object definiëert. Deze doet op zich niets, behalve wachten tot jij een instantie van dat gedefineerde object creëert.

De kracht van objecten is (zoals gezegd) namelijk dat je makkelijk meerdere instanties van hetzelfde object kan aanmaken.

Bekijk het onderstaande voorbeeld.

function User(name, code) {
   this.name = name;
   this.code = code;
}

Dit (ook wel constructor genoemd) definiëert een User object, met als parameters een name, en een code. de this constructie zorgt er in dit geval voor dat het object de properties name en code krijgt. Immers zouden name en code zonder iets ervoor gewone globale variabelen worden, en met var ervoor gewone lokale variabelen.

javascript kent geen Class (zoals Java), en een object moet dan ook gewoon gedefiniëerd worden als een normale function. Deze werkt ook precies hetzelfde als een normale functie. Het verschil zit in de aanroep.

var henk = new User('henk', 23);
var piet = new User('piet', 57);

Hiermee zijn henk en piet instanties van het object User geworden. Ze hebben alletwee de eigenschappen name en code, echter bevatten die (uiteraard) verschillende waarden:

henk.name == 'henk';
henk.code == 23;

piet.name == 'piet';
piet.code == 57;

this

In het voorbeeld hierboven worden er aan het User object eigenschappen toegevoegd met this. Dit is een vaste javascript constructie. this verwijst altijd naar het object waarbinnen de code uitgevoerd wordt.

<img name="plaatje" onclick="alert(this.name)" ... >

Bij dit voorbeeld hierboven is de onclick bijvoorbeeld een functie van het object image. Omdat de image een name heeft en omdat this in de onclick naar de image verwijst zal er in de alert plaatje staan.

Omdat de werkelijke code van een object slechts een blauwdruk is zal er dus binnen de object code met this naar een instantie verwezen moeten worden, alleen zo blijft de code generiek, en zullen de eigenschappen en functies voor alle toekomstige instanties gelden.

Prototyping

Toevoegen van object-specifieke functies

Naast eigenschappen heeft een object natuurlijk ook functies nodig die de eigenschappen van een object instantie kunnen aanpassen. Hiervoor moet de prototype van de constructor functie gebruikt worden.

Bekijk het onderstaande voorbeeld.

function User(name, code) {
   this.name = name;
   this.code = code;
}

   User.prototype.show = function() {
      alert(this.name + ': ' + this.code);
   }

piet = new User('piet', 57);
piet.show();

Door het prototype te gebruiken op het object kan je dus object specifieke functies toevoegen die zullen gelden voor alle toekomstige instanties van het object. Immers komt de variabele piet niet voor in de object code, en staat er in de alert wel piet: 57.

Binnen de toegevoegde functie zijn alle eigenschappen en andere toegevoegde functies van een object beschikbaar. Dit zorgt er dan ook voor dat er geen grote hoeveelheden globale variabelen meer nodig zijn om gegevens in op te slaan. Als er op de bovensaande manier bijvoorbeeld ook een update functie gedefiniëerd zou worden zou deze ook beschikbaar zijn vanuit de show en andersom.

// met het bovenstaande

   User.prototype.update = function(code) {
      this.code = code;
      this.show();
   }

piet.update(35);

Ook bestaande javascript objecten kunnen doormiddel van prototyping voorzien worden van meer functies. Zo zou op de volgende manier een functie aan het String object toegevoegd kunnen worden om een int terug te geven van de string (even afgezien van het nut ervan):

String.prototype.intValue = function() {
   return parseInt(this);
}

alert("2".intValue() + "3".intValue());   // 5

Opmerking

Een object functie zou ook op de volgende manier gedefiniëerd kunnen worden:

function User(name, code) {
   // ...

   this.show = function() {
      // ...
   }
}

Dit is op zich niet fout en het werkt precies hetzelfde als een functie die via de prototype is toegevoegd, alleen is deze manier stukken langzamer in uitvoer dan de prototype manier. De show functie maakt nu namelijk standaard geen deel uit van het object, en zal voor iedere instantie van het object apart worden toegewezen. Het gevolg is dus dat er meer dan nodig code wordt uitgevoerd, en dat er onnodig geheugen gebruikt wordt.

Objecten kopiëren

Een kopie maken van een object ?

Als je de waarde van een string of getal wil kopiëren naar een andere variabele gaat dat heel makkelijk:

var a = 10;
var b = a;
b += 1;

// b != a;

Als je ditzelfde bij een object doet gebeurt er dit:

var a = [1,2,3,4]; // Array
var b = a;

b[1] = 5;
alert(a);

// a == [1,5,3,4] == b

Er is dus geen kopie gemaakt, a en b verwijzen naar hetzelfde object, en door de waarde van b te wijzigen is a meeveranderd. Op zich is dit wel logisch. Stel dat b wel een kopie zou zijn geworden, dan zou dit een extreem brute regel code zijn:

var doc = document;

Zo zou immers het HELE document in 1 keer gekopiëerd worden, met alles wat daarin zit. Variabelen verwijzen dus slechts naar een object, en meerdere variabelen kunnen dus ook naar hetzelfde object verwijzen. Om een object werkelijk te kopiëren zul je de eigenschappen van het object uit moeten lezen, en deze in een nieuw object moeten stoppen, bijvoorbeeld zo:

var a = [1,2,3,4]; // Array
var b = copyObject(a);

function copyObject(obj) {
   var temp = [];
   for(var i in obj) {
      temp[i] = obj[i];
   }
   return temp;
}

b[1] = 5;
alert(a + ' != ' + b);

Bij ingewikkeldere objecten zou je waarschijnlijk een recursieve (zichzelf aanroepende) functie moeten gaan bouwen die ook de objecten binnen het object kopiëert. Al met al wordt het zo een behoorlijk omslachtige soep, waar je eigenlijk niet op uit bent. Het makkelijkste is dus eigenlijk om te voorkomen dat je ooit fysiek objecten moet kopieren. Maak liever een nieuwe instantie aan; Dat is immers de essentie van OO (object oriented) scripten.

Timers

Timeouts en Intervals

Dit is een kleine zijstap, maar het is onvermijdelijk dat je op een gegeven moment met timeouts of intervals te maken krijgt in combinatie met een Object structuur. Met een paar weetjes kan dit geen problemen meer opleveren. Bekijk het onderstaande voorbeeld.

function Counter() {
   this.time = 0;
   setInterval('this.update()', 1000);
}

   Counter.prototype.update = function(time) {
      this.time += time;
   }

In dit voorbeeld is er een Counter object, waarbij een interval de update functie van een instantie zou moeten aanroepen. Op deze manier gaat dit echter niet werken. De code die namelijk wordt uitgevoerd in de dikgedrukte regel is het zetten van de interval. De eerste this.update() wordt in dit geval dan ook pas na 1 seconde uitgevoerd.

De string in de interval wordt vervolgens ook globaal uitgevoerd, niet lokaal in een functie. this verwijst op dat moment dan niet naar de instantie van het object, maar naar window. In het onderstaande voorbeeld wordt dan ook de alert getoond, en wordt de update() van Counter nooit uitgevoerd:

function Counter() {
   this.time = 0;
   setInterval('this.update(1)', 1000);
}

   Counter.prototype.update = function(time) {
      this.time += time;
   }

function update() {
   alert(' dit is window.update() ')
}

new Counter();

Hoe het wel kan

Om toch via een interval of timeout een object specifieke functie aan te kunnen roepen kan je een object in een globale array zetten, en deze array in de interval of timeout gebruiken in plaats van this. Bekijk het onderstaande voorbeeld:

var counters = [];

function Counter(nr) {
   this.time = 0;
      
   counters[nr] = this;
   setInterval('counters['+nr+'].update(1)', 1000);
}

   Counter.prototype.update = function(time) {
      this.time += time;      
   }

new Counter(0);

Nadeel is dat je een globale variabele nodig hebt; Iets wat we juist wilden voorkomen met OO scripten. Maar natuurlijk zijn er nog meer manieren dan alleen deze, hierover meer in het onderdeel over Scope.

Samenvattend

Als je het niet gewend bent is het misschien even inkomen, maar als je het dan door hebt is het eigenlijk best makkelijk. Alles hierboven onder de knie? ga dan verder met overerving.

Peter Nederlof - peterned