Objecten 67 Wat is een object? 67 2.2 Een object creëren 70 2.3 Globale objecten en namen van typen 2.4 Prototype 74 Verschillen met Java 79 3.1 Typering 79 3.2 Klassen en objecten (facultatief) 80 3.3 Functioneel programmeren 82 2.1
3
Samenvatting Opdrachten Zelftoets
82 83
89
Terugkoppeling 1 2 3
58
90
Uitwerking van de opgaven 90 Uitwerking van de opdrachten 91 Uitwerking van de zelftoets 94
72
Leereenheid 7c
JavaScript: Objecten en functies
INTRODUCTIE
In de vorige leereenheden heeft u al functies gebruikt en gedefinieerd. In deze leereenheid vatten we dat nog eens samen, en gaan we dieper op het onderwerp in. Functies zijn in JavaScript waarden als alle andere waarden: een variabele kan als waarde een functie krijgen, functies kunnen als parameter worden meegegeven bij de aanroep van een functie, en functies kunnen zelfs worden teruggegeven. Een gedeelte van de stof over functies is facultatief: functies als parameter en functies als terugkeerwaarde vormen geen onderdeel van de verplichte leerstof. Omdat JavaScript met deze mogelijkheden zo anders is dan bijvoorbeeld Java stellen we u wel in de gelegenheid om er kennis van te nemen. JavaScript is een objectgeoriënteerde taal, in de zin dat afgezien van de waarden van de primitieve typen Boolean, Number en String alles in JavaScript een object is. De syntax van JavaScript lijkt weliswaar op die van Java, maar de manier van werken met objecten is wezenlijk anders: JavaScript kent bijvoorbeeld het begrip prototype. Als u in de leereenheid hierna leert werken met de DOM, het geheel van objecten dat de representatie is van de webpagina zoals de browser u die aanbiedt, is het handig om iets meer te weten over objecten in JavaScript. In deze leereenheid gaan we daarom ook dieper in op objecten. Tot slot zetten we een aantal verschillen tussen JavaScript en talen als Java en C# op een rijtje: programmeren met JavaScript verschilt in een aantal opzichten wezenlijk van dergelijke objectgeoriënteerde talen. Een gedeelte van de betreffende stof is facultatief. LEERDOELEN
Na het bestuderen van deze leereenheid wordt verwacht dat u – twee manieren kunt gebruiken om een functie te definiëren – een functie met optionele parameters kunt schrijven – een constructor kunt schrijven en gebruiken – twee manieren kunt gebruiken om een object te creëren – een methode kunt toevoegen aan het prototype van een object – de betekenis van de volgende begrippen kent: first-class elementen, anonieme functie, de ()-operator, dot-operator, []-notatie, object literal, property, methode, constructor, primitieve waarde, prototype, statische typering, dynamische typering. Studeeraanwijzingen Deze leereenheid staat los van het tekstboek. De paragrafen 1.3, 1.4 en 3.2 behoren niet tot de verplichte leerstof: ze zijn facultatief. We verwachten dat u voor het bestuderen van deze leereenheid ongeveer 9 uur zult nodig hebben.
OU
59
Webapplicaties: de clientkant
LEERKERN 1
Functies
1.1
SYNTAX - SAMENVATTING
We beginnen met een korte samenvatting van wat we tot nu toe van functies hebben gezien:
FIGUUR 7c.1
Een voorbeeld van een functiedefinitie
Functiedefinitie
Een functiedefinitie start met het keyword function, dan volgt de naam van de functie, gevolgd door de formele parameters tussen haakjes, met ten slotte de body, die tussen accolades staat. In de body kan gebruik gemaakt worden van de parameters, en er kan een waarde worden teruggegeven met behulp van het keyword return.
Functieaanroep
Een functie kan worden aangeroepen met behulp van de naam, gevolgd door haakjes met eventueel daar tussen de actuele parameters.
Formele parameters Actuele parameters
Formele parameters zijn de namen voor de parameters die gebruikt kunnen worden in de body van de functie. Actuele parameters zijn waarden die worden meegegeven bij de aanroep van de functie, en die bij de executie van de functie worden ingevuld waar in de body de namen van de formele parameters staan.
Optionele parameters
Er ontstaat geen fout als een functie wordt aangeroepen met meer of minder actuele parameters dan er formele parameters zijn gespecificeerd: het teveel aan parameters wordt gewoon genegeerd, en de parameters die te kort komen krijgen de waarde undefined. Deze eigenschap van JavaScript kan gebruikt worden om met optionele parameters te werken: function som(a, b, c) { return a + b + (c || 0); }
In dit voorbeeld is de derde parameter optioneel. Als de functie met drie actuele parameters wordt aangeroepen wordt de som van de drie parameters teruggegeven. Als de functie met maar twee parameters wordt gebruikt wordt de som van die twee parameters teruggegeven: de waarde van de derde wordt bij afwezigheid 0.
60
OU
Leereenheid 7c JavaScript: Objecten en functies
OPGAVE 7c.1
function eersteLetter(woord) { return woord.charAt(0); } var beginLetter = eersteLetter("Open Universiteit");
Wat zijn in bovenstaande code de formele en wat de actuele parameters? 1.2
FUNCTIES DEFINIËREN
Functies zijn ‘first-class’-elementen in JavaScript. Dat betekent dat een functie een waarde is met dezelfde eigenschappen als alle andere waarden. Dat heeft een aantal consequenties. Functie als waarde van variabele
In de eerste plaats kunt u een variabele als waarde een functie geven. In plaats van de notatie die we tot nu toe hebben gebruikt kunt u daarom ook schrijven: var som = function(a, b, c) { return a + b + (c || 0); };
Denk aan de puntkomma aan het einde: die hoort hier, net zoals bij elke andere toekenning! Anonieme functie
De functie heeft in dit geval geen naam: tussen het keyword function en de parameters staat geen naam. Een functie zonder naam heet een anonieme functie. De variabele som bevat nu als waarde een referentie naar de functie.
OPGAVE 7c.2
In de voorbeelden die u tot nu toe bent tegengekomen heeft u al vaak een toekenning uitgevoerd waarbij een variabele de waarde van een functie krijgt. Kunt u bedenken op welke plaatsen dat was? Functienaam is variabele
Als een functie gedefinieerd is met function functienaam(parameters) {}
is de situatie in feite precies hetzelfde. De functienaam is ook een variabele die een referentie bevat naar een functie, zoals figuur 7c.2 laat zien.
FIGUUR 7c.2
OU
Een functie en een variabele met een functie als waarde
61
Webapplicaties: de clientkant
Functienamen globaal
Functienamen van functies zoals we ze tot nu toe hebben gedefinieerd zijn dan ook globale variabelen. Een functie die gedefinieerd is als waarde van een variabele wordt op precies dezelfde manier aangeroepen als wanneer hij op de traditionele manier is gedefinieerd: var som = function(a, b, c) { return a + b + (c || 0); }; // som(1, 2, 3) geeft als waarde 6
De waarde kan ook aan een tweede variabele worden toegekend: var som = function(a, b, c) { return a + b + (c || 0); }; var tweedeSom = som; // tweedeSom(1, 2, 3) geeft als waarde 6
De variabelen som en tweedeSom verwijzen naar dezelfde functie. Er is een belangrijk verschil tussen: var x = som;
en: var x = som();
( )-operator
In het eerste geval krijgt de variabele x als waarde de functie met naam som. In het tweede geval krijgt de variabele x de terugkeerwaarde van de functie som als waarde. De haakjes hebben dus de betekenis ‘voer deze functie uit’: de ( )-operator. Een heel simpel voorbeeld is het volgende: function twee() { return 2; } var getal1 = twee; var getal2 = twee();
De waarde van getal1 is nu een functie met als body {return 2;}. De waarde van getal2 is nu 2. Dat het handig kan zijn om een functie als waarde toe te kennen aan een variabele laten we zien in het volgende voorbeeld. Een datum wordt in het Nederlands anders geformatteerd (dag – maand – jaar) dan in het Engels (maand – dag – jaar). Het is mogelijk om een functie formatteerDatum te schrijven waarin dat onderscheid wordt gemaakt:
62
OU
Leereenheid 7c JavaScript: Objecten en functies
function formatteerDatum(dag, maand, jaar) { var datum = ""; var taal = switch (taal) { case "en": var maandArray = ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"]; datum = maandArray[maand] + " " + dag.getDate() + ", " + jaar; break; case "nl": var maandArray = ["januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"]; datum = dag.getDate() + " " + maandArray[maand] + " " + jaar; } return datum; }
Als die functie vaak gebruikt wordt op een pagina wordt elke keer weer opnieuw opgezocht wat de taal is. De pagina zal daarom sneller functioneren als u in zo’n geval een variabele declareert, en die eenmalig, afhankelijk van de taal, een verschillende functie als waarde geeft: var formatteerDatum; var taal = switch (taal) { case "en": formatteerDatum = function(dag, maand, jaar) { var maandArray = ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"]; datum = maandArray[maand] + " " + dag.getDate() + ", " + jaar; return datum; }; break; case "nl": formatteerDatum = function(dag, maand, jaar) { var maandArray = ["januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"]; datum = dag.getDate() + " " + maandArray[maand] + " " + jaar; return datum; }; }
OU
63
Webapplicaties: de clientkant
Nu kunt u overal in de pagina een datum formatteren volgens de taal van de gebruiker met als aanroep: var datum = formatteerDatum(dag, maand, jaar);
Er hoeft dan niet, bij aanroep van de functie, opgezocht te worden wat de taal van de browser van de gebruiker is. Het op de juiste manier formatteren van de datum zal daardoor sneller gaan. OPGAVE 7c.3
Waarom zou er in het voorgaande voorbeeld een switch gebruikt zijn en niet een if-else-constructie? Samenvatting
– Een functie kan op twee manieren worden gedefinieerd: function functienaam(parameters) {…} var functienaam = function(parameters) {…};
– Namen van functies zijn globale variabelen. – De ( )-operator heeft als betekenis dat de functie waar hij achter staat wordt uitgevoerd. De rest van deze paragraaf is facultatief.
Er is nog een derde manier, waarbij u een functienaam gebruikt en tegelijkertijd een variabele de waarde van de functie geeft: var variabele = function naam(parameters){body};
Meestal zult u de eerste manier gebruiken. Het voorbeeld heeft laten zien dat u de tweede manier kunt gebruiken om eenmalig te kiezen voor de implementatie van een functie op een pagina. De derde manier heeft u nodig als u de tweede manier wilt gebruiken bij recursieve functies, maar daar zullen we in deze cursus niet verder op in gaan. Paragraaf 1.3 is facultatief.
1.3
FUNCTIE ALS PARAMETER
Een andere consequentie van het feit dat een functie een first-class element is, is dat functies als actuele parameter kunnen worden meegegeven in de aanroep van een functie. Het volgende voorbeeld maakt duidelijk waarom dat handig kan zijn. Het komt vaak voor dat we een array moeten aflopen en op elk element van de array een functie moeten toepassen. Om niet elke keer de for-lus te hoeven uitschrijven kunnen we een functie schrijven die dat eenmalig voor ons doet. De functie loopt de lijst af, bewerkt elk element met de meegegeven functie en geeft een lijst terug met de ‘bewerkte’ elementen. Daarbij maken we gebruik van het feit dat een functie als parameter kan fungeren: function toepassen(lijst, functie) { var resultaat = []; for (var i = 0; i < lijst.length; i++) { resultaat.push(functie(lijst[i])); } return resultaat; }
64
OU
Leereenheid 7c JavaScript: Objecten en functies
Deze functie wordt aangeroepen met een array en een functie als parameters, en geeft een array terug die bestaat uit de elementen van de meegegeven array waarop stuk voor stuk de meegegeven functie is toegepast. Als we de functie aanroepen moeten we erop letten dat de actuele parameter voor lijst een array is, en de actuele parameter voor functie de naam van een functie is, of een variabele die naar een functie wijst: function plus(getal) { return ++getal; } var getalLijst = [1, 2, 3]; // toepassen(getalLijst, plus) geeft nu [2, 3, 4]
Het is duidelijk dat het fout gaat als de actuele parameters van de functie toepassen geen array en functie zijn. Er is geen compiler die de typen controleert voordat we de code kunnen gebruiken in een webpagina. We zullen dus zelf moeten opletten of de typen van de argumenten kloppen met wat de functie verwacht. Sort met functie als parameter
In leereenheid 7b, paragraaf 7.4.2, heeft u de methode sort voor type Array gezien. Die methode sorteert de elementen in alfabetische volgorde. De methode werd, zoals we hem zagen, aangeroepen zonder argument, maar het is ook mogelijk om de methode sort aan te roepen met een functie als parameter. Die functie moet een negatieve waarde teruggeven als de eerste parameter voor de tweede moet komen, een positieve waarde als de eerste parameter na de tweede moet komen, en nul als de volgorde kan blijven zoals hij is. Om getallen op volgorde van groot naar klein te krijgen is het dus mogelijk om als functie te gebruiken: function() {return y-x;}
Bij het vergelijken van twee elementen komt de eerste na de tweede terecht als hij kleiner is, met andere woorden: de grootste waarde komt vooraan te staan. var lijst = [2, 5, 3, 9]; lijst = lijst.sort(); // lijst is nu: [2, 3, 5, 9] lijst = lijst.sort(function(x, y) {return y - x;}); // lijst is nu: [9, 5, 3, 2]
Een handige manier om de elementen van een array willekeurig te verplaatsen is daarom: lijst.sort(function() { return 0.5 - Math.random(); });
De uitkomst van 0.5 - Math.random()is immers afhankelijk van het toeval groter of kleiner dan nul.
OU
65
Webapplicaties: de clientkant
Paragraaf 1.4 is facultatief.
1.4
FUNCTIE ALS RETURNWAARDE
Zoals een functie als actuele parameter kan worden meegegeven bij een functieaanroep, zo kan een functie ook als returnwaarde worden teruggegeven. Het volgende voorbeeld maakt dat duidelijk. Het zal vaak voorkomen dat u in JavaScript een string tussen twee HTML-tags moet zetten: zo kan het voorkomen dat een door de gebruiker ingetypt stuk tekst tussen een begin- en eind p-tag moet komen te staan, of tussen een begin- en eind li-tag. Daartoe kunt u natuurlijk simpelweg een functie schrijven als: function tagDit(tag, tekst) { return "<" + tag + ">" + tekst + "" + tag + ">"; }
De functie aanroepen gaat dan met tagDit("li", "En dit is een link-item"); // Geeft terug: "
En dit is een link-item
"
Als we meerdere li-elementen op die manier moeten vullen wordt dat omslachtig: het zou mooi zijn als we over een functie zouden beschikken die we kunnen aanroepen als li(tekst); Zo’n functie is simpel te schrijven, maar het mooie van JavaScript is dat u een functie kunt schrijven waarmee u in één keer beschikt over speciale functies voor alle mogelijke tags. Dat gaat op de volgende manier: function tagDit(tag) { var begintag = "<" + tag + ">"; var eindtag = "" + tag + ">"; return function(tekst) { return begintag + tekst + eindtag; }; } var li = tagDit("li"); var liElement = li("en dit is een li"); // levert op: "
en dit is een li
"
Wat hier gebeurt is het volgende: de variabele li krijgt als waarde de terugkeerwaarde van de functie tagDit, aangeroepen met de actuele parameter "li". Die terugkeerwaarde is een anonieme functie, met een formele parameter tekst. In de body van die functie hebben begintag en eindtag de waarde "
" en "
" gekregen. De body van de functie die de variabele li als waarde gekregen heeft luidt dus: return "
" + tekst + "
";.
Het handige hiervan is dat we nu functies hebben voor alle mogelijke tags: var strong = tagDit("strong"); var strongElement = strong("Dit moet vet"); // levert op: "<strong> Dit moet vet " var p = tagDit("p"); var element = p("Dit is een alinea"); // levert op: "
Dit is een alinea
"
66
OU
Leereenheid 7c JavaScript: Objecten en functies
Property
2
Objecten
2.1
WAT IS EEN OBJECT?
Een object in JavaScript is een verzameling gebundelde properties. Een property (attribuut, eigenschap) bestaat uit een naam en een waarde. Een simpel voorbeeld van een object is: var adres = { straat: "Valkenburgerweg", nummer: 177, postcode: "6419AT", plaats: "Heerlen" };
De accolades geven aan dat de waarde van de variabele adres een referentie naar een object is. Een variabele die als waarde een object heeft, is in werkelijkheid altijd een referentie naar een object, zoals u in leereenheid 7a heeft gezien:
FIGUUR 7c.3
De variabele adres is een referentie naar een object
Tussen de accolades staan de properties. De properties worden gescheiden door komma’s; achter de laatste property staat geen komma. Een property wordt gedefinieerd door een naam te laten volgen door een dubbele punt en een waarde: propertynaam: propertywaarde,
In het geval van het object adres heeft de property nummer een waarde van type Number, en de andere properties hebben waarden van type String. Dot-operator
Om aan de waarde van een property van een object te komen gebruikt u de dot-operator: var ouPlaats = adres.plaats;
De betekenis van de dot-operator is dus: ‘object, geef me de waarde van je property met deze naam’. Om de waarde van een property te veranderen gebruikt u ook de dotoperator: adres.plaats = "Groningen";
De betekenis van de dot-operator is hier: ‘object, geef me toegang tot de waarde van je property met deze naam’.
OU
67
Webapplicaties: de clientkant
OPGAVE 7c.4
Waar bent u de dot-operator al tegengekomen? Property toevoegen
Het is in JavaScript heel gemakkelijk om properties toe te voegen aan een bestaand object. Dat gebeurt ook met de dot-operator. adres.land = "Nederland";
FIGUUR 7c.4
[]-notatie
Een property toevoegen aan een object
Er is een tweede manier om aan de waarde van een property te komen. De notatie lijkt op de manier waarop je een element van een array bereikt, maar gebruikt een string (de naam van de property) in plaats van een index: var ouPlaats = adres["plaats"];
De notatie met de dot-operator is gemakkelijker te lezen; de notatie met [ ] heeft als voordeel dat je binnen de haken een variabele kunt gebruiken: var adresdeel = "plaats"; adres[adresdeel] = "Maastricht";
Programmeeraanwijzing Gebruik de dot-operator om aan de waarde van een property te komen of om een property toe te voegen, tenzij u gebruik wilt maken van het feit dat u met de [ ]-notatie een variabele tussen de haken kunt gebruiken. Property is geen globale variabele
Een property is geen globale variabele. Als de naam van een property gebruikt wordt zonder een object ervoor (preciezer gezegd: zonder de naam van een variabele ervoor die als waarde een referentie naar een object heeft), zal de waarde ervan undefined zijn. Een property met dezelfde naam als een globale variabele zal deze globale variabele dus niet overschrijven, en andersom is dat ook niet het geval. De waarde van een property kan van elk type zijn dat beschikbaar is in JavaScript: Number, Boolean, String, Array, Object, function of null. En net zoals bij variabelen krijgt een property die niet is gedefinieerd de waarde undefined.
Methode
Als het type van een property function is, heet de property een methode van het object. Een methode is een functie die hoort bij een object, en die kan worden aangeroepen met de dot-operator of met de [ ]-notatie.
68
OU
Leereenheid 7c JavaScript: Objecten en functies
Een voorbeeld van een methode is: var adres = { straat: "Valkenburgerweg", nummer: 177, postcode: "6419AT", plaats: "Heerlen", formatteer: function() { return this.straat + " " + this.nummer + " " + this.postcode + " "+ this.plaats; } }; NB: In feite omvat de term property dus ook methoden. Een methode is tenslotte een property met als waarde een functie. In de praktijk wordt er meestal gesproken over ‘properties en methoden’, alsof een methode iets anders is dan een property. Dat zullen we in de cursus over het algemeen ook doen.
this
Pas op bij methode als event handler
In de methode formatteer is het keyword this te zien. Dat keyword wordt gebruikt om binnen de definitie van een methode te verwijzen naar het object waar de methode bij hoort. Als de properties zouden worden gebruikt zonder het keyword this ervoor, zou er verwezen worden naar globale variabelen straat, nummer, postcode en plaats, en dat is niet de bedoeling (die zullen in de meeste gevallen ook niet bestaan). Let dus op bij het gebruik van het keyword this in methoden: dat moet niet gebruikt worden als een methode als event handler wordt gebonden aan een event. Het keyword this wijst in dat geval naar het element waarin de event plaatsvond!
Programmeeraanwijzing Gebruik liever geen methode van een object als event handler; gebruik alleen een losse functie. Binnen zo’n functie kunt u natuurlijk wel methoden van objecten aanroepen. Samenvatting
– Een object kan als volgt worden gecreëerd: var variabelenaam = { propertynaam-1: propertywaarde1, …, propertynaam-n: propertywaarden }
– De punt heet de dot-operator. Met behulp van de dot-operator kunt u: – de waarde van een property opvragen – de waarde van een property veranderen – een property toevoegen. – Naast de dot-operator kunt u ook de [ ]-notatie gebruiken om de waarde van een property op te vragen, of te wijzigen, of een property toe te voegen. Binnen de [ ]-notatie kunt u een variabelenaam gebruiken. – Als de waarde van een property een functie is, spreken we van een methode. – Het keyword this verwijst naar het bijbehorende object.
OU
69
Webapplicaties: de clientkant
2.2
EEN OBJECT CREËREN
Het ligt voor de hand dat in een applicatie met een object voor een adres meerdere adressen nodig zijn. Het is natuurlijk mogelijk om voor elke variabele die nodig is met properties en waarden te knippen en te plakken, maar mooi is dat niet. In JavaScript is het mogelijk om objecten te creëren met behulp van een constructor.
Een nieuw Adres-object maken gaat nu als volgt: var ouAdres = new Adres("Valkenburgerweg", 177, "6419AT", "Heerlen");
Elk Adres-object dat op deze manier wordt gecreëerd beschikt over een eigen kopie van eventuele methoden die binnen de constructor zijn gedefinieerd, zoals in dit voorbeeld de methode formatteer. Het is dus wel mogelijk om een methode te definiëren in een constructor, maar om efficiencyredenen is het niet aan te raden. Hoe dat efficiënter kan komt in paragraaf 2.4 aan bod. Programmeeraanwijzing Neem geen methoden op in een constructor, omdat elk gecreëerd object er een kopie van krijgt. Het type van ouAdres na deze toekenning is dat van Adres-object. Door een constructor te definiëren creëert u dus een nieuw type. Constructor is een functie Kenmerken constructor
this
70
Een constructor is dus een functie. Er is een aantal verschillen met de functies die we tot nu toe zijn tegengekomen: – De constructor wordt aangeroepen met het keyword new. – Er is geen statement met het keyword return, terwijl er wel een waarde wordt teruggegeven: een object. Hier zit een addertje onder het gras: als de constructor zonder het keyword new zou worden aangeroepen, zou hij als een ‘gewone’ functie fungeren, en zou er geen object worden teruggegeven. Het keyword new zorgt er dus voor dat dit gebeurt! – In de functiedefinitie wordt het keyword this gebruikt. Als de constructor wordt gebruikt om een object te creëren verwijst dat keyword this naar het gecreëerde object.
OU
Leereenheid 7c JavaScript: Objecten en functies
Ook hier zit een addertje onder het gras: als de constructor gebruikt wordt zonder het keyword new, zal het keyword this verwijzen naar het object waarbinnen de functie wordt uitgevoerd. Als er niet zo’n object is verwijst het keyword this naar de globale omgeving. Als er globale variabelen bestaan met dezelfde naam als de properties van het object waarvoor de constructor bedoeld is, zullen die overschreven worden als de constructor per ongeluk zonder het keyword new wordt aangeroepen. Constructor begint met hoofdletter
Het is dus belangrijk om een constructor nooit als ‘gewone’ functie aan te roepen, maar altijd met het keyword new. Om die reden is de gewoonte ontstaan om constructors met een hoofdletter te laten beginnen. De hoofdletter is een geheugensteuntje, zodat u weet dat het niet om een gewone functie gaat, maar om een constructor. De syntax van JavaScript schrijft dat niet voor, maar het voorkomt vergissingen.
Programmeeraanwijzing Begin de naam van een constructor met een hoofdletter, en gebruik een constructor nooit zonder het keyword new. Object creëren met literal
Er zijn in JavaScript dus twee manieren om objecten te creëren: – met een literal (letterlijke waarde) , zoals in: var rechthoek = { zijde1: 4, zijde2: 3 };
Object creëren met constructor
– met een constructor zoals in: function Rechthoek(eneZijde, andereZijde) { this.zijde1 = eneZijde; this.zijde2 = andereZijde; } var eenRechthoek = new Rechthoek(4, 3);
Als u voldoende heeft aan één object is de literal in het voordeel: er is minder code nodig, en de code is duidelijk. Als u meer objecten nodig heeft is een constructor handiger. Programmeeraanwijzing Gebruik alleen een constructor als u meerdere objecten nodig heeft; gebruik voor slechts één object een literal. Samenvatting
– Een object kan behalve als literal ook gecreëerd worden met behulp van een constructor. U roept de constructor aan met het keyword new. – Een constructor is een functie zonder return-statement. Als de constructor met het keyword new wordt aangeroepen geeft hij een object terug. – Het keyword this binnen een constructor verwijst naar het op te leveren object, als de constructor met het keyword new wordt aangeroepen.
OU
71
Webapplicaties: de clientkant
2.3
Globale objecten
GLOBALE OBJECTEN EN NAMEN VAN TYPEN
Er is een aantal objecten in JavaScript die u kunt gebruiken ook al hebt u ze niet gecreëerd: ze bestaan altijd. Die objecten worden globale objecten genoemd. De weblink geeft een overzicht. Deze globale objecten kunnen methoden hebben: u bent er al een aantal tegengekomen in de leereenheden 7a en 7b. Daarin kwam bijvoorbeeld het object Math voor. De methoden uit tabel 7.14 uit het tekstboek zijn methoden van dat globale object Math. Andere voorbeelden zijn het object String, het object Array, het object Boolean en het object Date. In tabel 7.12 van het tekstboek heeft u de methode fromCharCode gezien, van het globale object String. Die methode kunt u dus niet aanroepen op een string, alleen op het globale object String.
Weblink: Global Objects
Met de termen Array, String, Boolean en dergelijke kunnen dus verschillende zaken worden aangeduid. We werken dat hier uit voor de term String: String als type
var tekst = "Dit is een waarde van type String.";
De variabele tekst heeft hier een waarde van type String. Die waarde is geen object. Bij een toekenning wordt er een kopie van de waarde gecreëerd. var kopieTekst = tekst;
De variabele kopieTekst krijgt als waarde een kopie van de waarde van de variabele tekst. Bij het veranderen van de waarde van kopieTekst verandert de waarde van tekst niet. Hetzelfde geldt voor Boolean en Number: waarden kunnen van type Boolean zijn, of van type Number. Dit zijn de primitieve (enkelvoudige) typen binnen JavaScript. String als String object
Een waarde kan ook van type String object zijn. var tekst = new String("Dit is een waarde van type String object");
Het onderscheid tussen waarden van type String en van type String object is erg belangrijk bij toekenningen en vergelijkingen. Bekijk bijvoorbeeld de volgende code: var var var var
tekstA = "Dit is een waarde van type String"; tekstB = "Dit is een waarde van type String"; tekstObjectA = new String("Dit is String Object"); tekstObjectB = new String("Dit is String Object");
De expressie (tekstA == tekstB) zal true opleveren, maar de expressie (tekstObjectA == tekstObjectB) levert false op: tekstA en tekstB hebben een waarde van type String, en die waarde is in beide gevallen hetzelfde, maar tekstObjectA en tekstObjectB bevatten beide een referentie naar een object: naar twee verschillende objecten.
72
OU
Leereenheid 7c JavaScript: Objecten en functies
Het is, vanwege dit gedrag, handiger om zoveel mogelijk gebruik te maken van het primitieve type String, maar u moet er op bedacht zijn dat u, als u niet alle code zelf hebt geschreven, ook waarden van type String object kunt tegenkomen. Primitieve waarde Weblink: Methode valueof
Met behulp van de methode valueOf kunt u van elk object de primitieve waarde van het object opvragen. Bij het vergelijken van twee objecten van type Boolean object, Number object of String object is het dus belangrijk om de waarden met elkaar te vergelijken die het toepassen van valueOf oplevert. Als tekstA, tekstB, tekstObjectA en tekstObjectB de waarden bevatten zoals ze hiervoor zijn gegeven geldt het volgende: tekstA == tekstB // levert true tekstObjectA == tekstObjectB // levert false tekstObjectA.valueOf() == tekstObjectB.valueOf() // levert true
Constructor String
Impliciete typeconversie
Het zal u zijn opgevallen dat het woord String ook de constructor String kan aangeven. In feite is de constructor String hetzelfde als het globale object String: elke functie is ook een object in JavaScript, en een object kan properties en methoden bevatten. Als u dus String.fromCharCode gebruikt, maakt u in feite gebruik van een methode van de constructor String. In leereenheid 7a en 7b heeft u methoden aangeroepen op waarden van het primitieve type String. Goed beschouwd is dat vreemd: alleen objecten kennen methoden. Nu kunt u beredeneren wat er in zo’n geval gebeurt: de waarde van type String wordt door impliciete typeconversie omgezet in een waarde van een String object. Vervolgens kan de methode op dat object worden aangeroepen. De property length en de methoden die voor type String in leereenheid 7a werden besproken, zijn dus properties van een String object. Zoals er waarden van type String en van type String object bestaan, geldt dat ook voor Boolean (type Boolean en type Boolean object) en Number (type Number en type Number object). Waarden van type Array, Object en function zijn altijd objecten; type String, type Boolean en type Number zijn primitieve typen. Waarden van de overige typen zijn (specialisaties van) objecten.
OPGAVE 7c.5
Wat gebeurt er precies bij het uitvoeren de volgende code? var nummer = 3; var getal = nummer.toString(); Samenvatting
– De term String, kan in een aantal verschillende betekenissen worden gebruikt: – een waarde van type String: var tekst = "Dit is een tekst";
OU
73
Webapplicaties: de clientkant
– het globale object String: var tekst = String.fromCharCode(65,66,67); // tekst krijgt als waarde "ABC"
Dit globale object String is hetzelfde als de constructor die objecten van type String object creëert: var tekst = new String("een string");
– een waarde van type String object: var tekst = new String("een string");
– Met de methode valueOf is de primitieve waarde te krijgen van waarden van objecten. 2.4
PROTOTYPE
Drie voorbeelden van het gebruik van prototype
Voor we uitleggen wat het begrip prototype in JavaScript inhoudt, laten we aan de hand van drie voorbeelden zien waarom zoiets handig zou kunnen zijn.
Methode toevoegen aan type
Stel, u heeft een functie geschreven met een array als parameter: function last(lijst) { if (lijst.length) { return lijst[lijst.length-1]; } }
U kunt die in uw code uiteraard gebruiken, maar het zou mooier zijn als u er een methode van arrays van zou kunnen maken, die u op dezelfde manier zou kunnen aanroepen als andere array-methoden. De functie last zoals hij hier is gedefinieerd levert een probleem op als hij per ongeluk met een waarde wordt aangeroepen van een ander type dan Array, en dat probleem is er niet als last een array-methode zou zijn. JavaScript kent een mechanisme, prototype, waarmee dat mogelijk is: elke array in JavaScript heeft een referentie naar het prototype voor arrays, en als u een methode toevoegt aan dat prototype, beschikt elke array over die methode: Array.prototype.last = function() { if (this.length) { return this[this.length-1]; } }
U kunt nu aan het laatste element van een array komen met: var element = lijst.last();
74
OU
Leereenheid 7c JavaScript: Objecten en functies
Default-waarde voor property
Een tweede voorbeeld is het volgende. Stel, u heeft een constructor Adres geschreven. U voorziet dat de waarde van de property land, van objecten van type Adres object, in vrijwel alle gevallen "Nederland" zal zijn. U kunt dan aan elk object die property toevoegen, in de constructor, met die waarde. Elk object krijgt dan een eigen property land. Efficiënter is het om die property toe te voegen aan het prototype van Adres-objecten. Als u de property land opvraagt van een willekeurig Adres-object, zal dat object zelf niet over zo’n property beschikken, maar het zal de waarde van de property land van z’n prototype teruggeven. Adres.prototype.land = "Nederland"; var eenAdres = new Adres(); // waarde van eenAdres.land is "Nederland"
Het voordeel hiervan is dat u, bij een uitzondering, een object zelf een property land kunt geven, met een andere waarde. Alle andere Adresobjecten zullen dan "Nederland" als waarde teruggeven van hun property land. var duitsAdres = new Adres(); duitsAdres.land = "Duitsland"; // waarde van duitsAdres.land is "Duitsland" // waarde van eenAdres.land is "Nederland" Type erft methoden van een ander type
Met behulp van een prototype is het ook mogelijk om in JavaScript een vorm van overerving te gebruiken. Stel, u heeft een constructor voor Zoogdieren opgesteld, waarbij u een methode isJarig heeft toegevoegd aan het prototype: function Zoogdier(naam){ this.naam = naam; this.leeftijd = 0; } Zoogdier.prototype.isJarig = function(){ this.leeftijd += 1; };
U wilt nu een constructor Kat opstellen, en daarbij wilt u dat een Kat over de methoden van Zoogdier beschikt. Dat lukt met behulp van het prototype: function Kat(naam, ras){ this.naam = naam; this.ras = ras; } Kat.prototype = new Zoogdier(); Kat.prototype.constructor = Kat;
Nu is het mogelijk om nieuwe Katten te creëren, die uit zichzelf al jarig kunnen zijn: var minoes = new Kat("Minoes", "lapjeskat"); minoes.isJarig(); // minoes.leeftijd heeft nu als waarde 1
OU
75
Webapplicaties: de clientkant
Prototype
Elk object in JavaScript heeft automatisch een speciale property die verwijst naar een object: het prototype. Elke array bijvoorbeeld bevat zonder dat we daar iets voor hoeven te doen een extra property: het prototype. We kunnen niet aan de property prototype van de array komen: als de variabele lijst als waarde een array heeft zal lijst.prototype als waarde undefined geven. Maar ‘onder water’ bestaat de property wel, en de variabele lijst kan er gebruik van maken. Het bijzondere van het prototype van een object is dat het object niet alleen de beschikking heeft over de eigen properties en methoden, maar ook over de properties en de methoden van het prototype. Als we aan adres een property vragen die het zelf niet heeft, zal het kijken of z’n prototype wel over de property beschikt. Is dat het geval, dan geeft het die property terug. Array.prototype.last = function() { if (this.length) { return this[this.length-1]; } } var lijst = [1, 2, 3]; var element = lijst.last();
Een array beschikt zelf niet over de methode last. Maar als we die aan het prototype van Array toevoegen, kan elke array die methode gebruiken alsof het een eigen methode is.
FIGUUR 7c.5
Twee objecten met een prototype
Figuur 7c.5 laat twee objecten zien die beide hetzelfde prototype hebben. Beide beschikken ze daarom als het ware over een extra property land. De waarde daarvan is niet binnen elk object opgeslagen, maar op slechts één plek: in het prototype-object. Het is voor elk object mogelijk om het object zelf een property land te geven. De objecten die zelf niet over die property beschikken ‘zien’ alleen de property land van hun prototype. De objecten die wel over die property beschikken ‘zien’ alleen hun eigen property.
76
OU
Leereenheid 7c JavaScript: Objecten en functies
Prototype bereiken via constructor
Het prototype van een object is dus niet direct te bereiken via dat object. We kunnen het prototype wel bereiken via de constructor van het object. Via de constructor kunnen we, via een property prototype, aan de waarde van het prototype van de te creëren en gecreëerde objecten komen. In het voorbeeld met de array heeft u dat ook gezien: we voegden de methode last toe aan de constructor van arrays: Array.prototype.last = function() { if (this.length) { return this[this.length-1]; } }
FIGUUR 7c.6
Constructor met een prototype
Bij het gebruiken van het keyword new bij het creëren van een object met behulp van een constructor krijgt het gecreëerde object een ‘geheime’ property met als waarde het prototype mee, zoals Figuur 7c.6 laat zien. Property toevoegen aan prototype
Een prototype is een object als alle andere objecten. We kunnen dus ook een extra property toevoegen aan zo’n prototype. Het is daardoor mogelijk om constanten die voor alle objecten van een bepaald type gelden, variabelen die voor alle objecten van een bepaald type dezelfde waarde hebben, of methoden die ook voor alle objecten van een bepaald type hetzelfde zijn, te definiëren in het prototype. U voegt bijvoorbeeld een methode toe aan een prototype voor Arrayobjecten op de volgende manier: Array.prototype.last = function() {...};
In een aantal gevallen is het handig om een property niet te definiëren in een constructor, maar in het prototype:
OU
77
Webapplicaties: de clientkant
Default-waarden
– Via een prototype kunt u default-waarden creëren. Het prototype van Adres geven we bijvoorbeeld een property land: "Nederland". In de constructor hoeft de property land dan niet te worden opgenomen. function Adres(straat, nummer, postcode, plaats){ this.straat = straat; this.nummer = nummer; this.postcode = postcode; this.plaats = plaats; }; Adres.prototype.land = "Nederland";
Alle objecten van type Adres object beschikken daardoor via hun prototype over een property land met als waarde "Nederland". Willen we bij een variabele britishOUAdres een ander land opgeven, dan geven we dat object een eigen property land: britishOUAdres.land = "United Kingdom"; Gemeenschappelijke waarden
– Alles wat gemeenschappelijk is voor alle objecten die met een bepaalde constructor worden gecreëerd kan het beste in het prototype worden ondergebracht: in dat geval bestaat er maar één kopie van. – Een voorbeeld van wat gemeenschappelijk is, zijn methoden. Door methoden in het prototype te definiëren is het overbodig om in een constructor methoden op te nemen: het is ongewenst dat elk object over zijn eigen kopie van de methode beschikt. Het toevoegen van een methode aan het prototype van een constructor is dus iets anders als het toevoegen van methoden aan de constructor zelf!
Methode toevoegen aan constructor
Het verschil laten we hier zien met een constructor Auto: function Auto(merk, type) { this.merk = merk; this.type = type; } var prius = new Auto("Toyota", "Prius"); Auto.pkToKw = function(pk){return pk * 0.746;}; // Auto.pkToKw(70) levert op: 52.22 // prius.pkToKw(70) levert een fout op
Methode toevoegen aan prototype
Als de methode pkToKw wordt toegevoegd aan het prototype van de constructor hebben objecten van type Auto object de methode wel tot hun beschikking: function Auto(merk, type) { this.merk = merk; this.type = type; } var prius = new Auto("Toyota", "Prius"); Auto.prototype.pkToKw = function(pk){return pk * 0.746;}; // prius.pkToKw(70) levert op: 52.22
78
OU
Leereenheid 7c JavaScript: Objecten en functies
De eerste manier (een methode toevoegen aan de constructor) heeft de voorkeur als de methode interessant is buiten de objecten van type Auto object om; de tweede manier (een methode toevoegen aan het prototype) heeft de voorkeur als de methode alleen interessant is voor objecten van type Auto object. Programmeeraanwijzing Definieer methoden en gemeenschappelijke waarden niet in een constructor, maar in het prototype van het object dat door de constructor wordt gecreëerd. Weblink: Prototype
Het is mogelijk om het prototype als waarde een object te geven. De weblink brengt u bij meer informatie daarover.
Samenvatting
– Elk object beschikt over een prototype. Een object kan de properties en methoden van z’n prototype gebruiken alsof het z’n eigen properties en methoden zijn. – Het prototype van objecten is te bereiken via de constructor van die objecten. Via die constructor is het mogelijk om: – properties en methoden aan het prototype toe te voegen – properties en methoden van het prototype te veranderen. – Het is aan te bevelen om methoden niet in een constructor te definiëren, maar aan het prototype toe te voegen. – Constanten kunnen als property aan het prototype worden toegevoegd. – Properties van het prototype zijn handig om default-waarden voor properties vast te leggen voor objecten. 3
Objectgebaseerd of objectgeoriënteerd?
Verschillen met Java
JavaScript wordt soms wel en soms niet objectgeoriënteerd genoemd: dat hangt af van iemands definitie van objectoriëntatie. Wat duidelijk is, is dat JavaScript objectgebaseerd is: JavaScript kent het begrip object als een verzameling properties en methoden, en behalve de primitieve typen Boolean, Number en String is alles binnen JavaScript van het type Object, waarbij arrays en functies speciale vormen van een object zijn. De verschillen met Java zitten hem in de dynamische typering van JavaScript, in de rol van functies binnen JavaScript, en in de andere aspecten van objectoriëntatie: klassen, encapsulation, inheritance en polymorfisme. Vanwege verschillen op die laatste aspecten wordt JavaScript vaak objectgebaseerd in plaats van objectgeoriënteerd genoemd. 3.1
JavaScript is, zoals we hebben gezien, dynamisch getypeerd: het type van een variabele wordt bepaald door het type van de waarde van de variabele. Java is statisch getypeerd: het type van een variabele wordt bepaald door de manier waarop de variabele is gedeclareerd.
OU
79
Webapplicaties: de clientkant
Tijdens het compileren van Java worden allerlei fouten met de typering van variabelen geconstateerd. Met JavaScript gebeurt dat niet, ook niet op het moment dat de JavaScript interpreter een JavaScriptbestand inleest. OPGAVE 7c.6
Waarom is het declareren van een variabele voor hij een waarde krijgt wel nodig in Java maar niet in JavaScript? JavaScript: impliciete typeconversie
Een ander verschil op het gebied van typering is dat JavaScript in veel grotere mate impliciete typeconversie kent. Dat maakt het extra lastig om fouten op het gebied van typering te ontdekken: zolang JavaScript het type van een waarde kan converteren naar het type dat wordt verwacht, ontstaat er geen fout. In Java kan de programmeur kan door middel van een expliciete cast een variabele tijdelijk een ander type geven. In JavaScript is dat niet nodig.
Java: cast
Paragraaf 3.2 is facultatief.
3.2
KLASSEN EN OBJECTEN
Zowel Java als JavaScript kennen objecten: datastructuren met properties en methoden. Er is een aantal verschillen. JavaScript: dynamische objecten
In JavaScript zijn objecten dynamisch: tijdens de duur van het programma kan een object extra properties en methoden krijgen, properties en methoden kunnen van naam veranderen, of worden weggehaald. In Java zijn objecten statisch. De waarde van een property (attribuut in Java) kan wel veranderen, maar een object behoudt de properties en methoden die het heeft wanneer het gecreëerd wordt. De body van methoden verandert niet. In Java kan de klasse van een variabele daarom als een contract fungeren: ‘ik ben van deze klasse, dus ik beschik over deze methoden’. In JavaScript is dat uitgesloten: ook al weet je hoe het object er bij creatie uitzag, je weet nooit zeker over welke methoden het beschikt.
Java: statische objecten
Java: encapsulation
JavaScript: geen encapsulation
Weblink: Module pattern
80
Encapsulation, inkapseling, houdt in dat properties of methoden van een object verborgen gehouden kunnen worden voor de buitenwereld. In Java kunnen attributen en methoden door middel van het keyword private verborgen worden gehouden. De buitenwereld heeft dan geen kennis over de exacte manier waarop een object in elkaar zit: de buitenwereld heeft alleen weet van de interface van het object: attributen en methoden die als public zijn gedeclareerd. Dat heeft als voordeel dat het mogelijk is om een object anders in te richten (via de klasse). Zolang de interface maar hetzelfde blijft, heeft zo’n verandering geen invloed op de rest van het programma. JavaScript kent niets dat overeenkomt met het keyword private: JavaScript kent geen encapsulation. Het is wel mogelijk om met behulp van ingewikkelde constructies met functies private properties en methoden te simuleren. Hoe dat gebeurt valt buiten het bestek van deze cursus. De manier waarop dat gebeurt wordt het module pattern genoemd. De taal zelf beschikt niet over constructies speciaal daarvoor.
OU
Leereenheid 7c JavaScript: Objecten en functies
Java: klassen Prototypegebaseerd Constructor is geen klasse
Klasse als template
Constructor is functie
In Java is elk object een instantie van een klasse. Klassen zijn georganiseerd in een klassenhiërarchie, met één klasse aan de top. JavaScript kent geen klassen: het is een prototypegebaseerde taal. Wat daarbij verwarrend kan zijn, is dat JavaScript het begrip constructor kent, dat we objecten kunnen creëren met het keyword new, en dat constructors over het algemeen met een hoofdletter worden geschreven. Een constructor is echter geen klasse! Een klasse in Java is een template voor objecten, die methoden kan bevatten en attributen. De attributen kunnen klassenvariabelen zijn (met het keyword static) of instantievariabelen. Instanties van klassen bevatten attributen, maar hebben geen eigen kopie van de methoden. Een constructor in JavaScript is een functie die een object oplevert. Het object zal alle properties bevatten die in de constructor worden geïnstantieerd, en zal ook een kopie bevatten van eventuele methoden die in de constructor zijn gedefinieerd.
OPGAVE 7c.7
Is het in Java mogelijk om een object te creëren dat niet bij een bepaalde klasse hoort? Is het in JavaScript mogelijk een object te creëren zonder constructor? Met het begrip klasse hangt een aantal andere begrippen samen. Java: inheritance JavaScript: erven van prototype
Java: beperkt polymorfisme
Polymorfisme in Java houdt in dat een object, bijvoorbeeld gedeclareerd als Animal, zich kan gedragen als Dog of Cat, afhankelijk van de waarde van het object op dat moment. In Java is polymorfie beperkt: een variabele die gedeclareerd is als een bepaalde klasse kan als waarde een subklasse daarvan krijgen, en zich als die subklasse gedragen. In JavaScript is polymorfisme onbeperkt: elke variabele kan elk type als waarde krijgen, en zal zich gedragen afhankelijk van het type.
JavaScript: onbeperkt polymorfisme Java: name space Weblink: Name space
JavaScript: alles globaal
Overerving of inheritance zorgt ervoor dat een klasse over de properties en methoden van een superklasse kan beschikken. JavaScript kent geen klassen en geen klassenhiërarchie. Inheritance zoals bij Java kent JavaScript dus niet. Objecten erven wel properties en methoden; die erven ze van hun prototype. Dat prototype is bovendien dynamisch: tijdens de duur van een programma kunnen de properties en methoden van een prototype veranderen, omdat een prototype een object is.
In Java is het mogelijk om met behulp van packages een name space te creëren waarbinnen klassennamen uniek zijn, zodat er geen problemen optreden wanneer de code vanuit andere applicaties gebruikt wordt. In JavaScript bestaan geen packages, en elk JavaScriptbestand waaraan vanuit een HTML-bestand wordt gerefereerd ‘woont’ in dezelfde name space. In principe zijn alle namen globaal. Het is daarom van groot belang om globale variabelen en globale functies zoveel mogelijk te vermijden. Vanwege met name de verschillen op het gebied van inheritance en encapsulation wordt vaak gesteld dat JavaScript niet objectgeoriënteerd is, maar alleen objectgebaseerd, of prototypegebaseerd.
OU
81
Webapplicaties: de clientkant
3.3
FUNCTIONEEL PROGRAMMEREN
Een belangrijk verschil tussen Java en JavaScript is dat functies in JavaScript first-class elementen zijn: functies kunnen gebruikt worden als parameters in functies, en kunnen teruggegeven worden bij een functieaanroep (de facultatieve paragrafen 1.3 en 1.4 laten zien hoe). Functies als first-class elementen is het centrale idee bij functionele programmeertalen. Om te leren welke krachtige constructies er mogelijk zijn dankzij dit feit, is het aan te bevelen een functionele taal te leren: binnen deze cursus is er geen plaats om daar uitgebreid aandacht aan te besteden.
SAMENVATTING
– Een functie heeft een naam, heeft eventueel parameters, en een body. In de body kan een waarde worden teruggegeven. – Een functie is een waarde als alle andere waarden. Een variabele kan dus als waarde ook een functie krijgen. – De ( )-operator heeft als betekenis: ‘voer deze functie uit’. – Een object is een verzameling properties. Een property met als waarde een functie heet een methode. Een object kan worden geïnstantieerd met een literal of met een constructor. – De dot-operator geeft toegang tot properties (en dus ook tot methoden) van een object. – Elk object beschikt over een prototype. Properties en methoden van het prototype zijn beschikbaar voor het object. Het prototype van objecten is te manipuleren via de constructor van de objecten. – De term String kan betrekking hebben op: – type String, – type String object, – het globale object String: de constructor String. Datzelfde geldt voor de termen Boolean en Number. – De term Array kan betrekking hebben op: – type Array object, – het globale object Array: de constructor Array. Datzelfde geldt voor de termen Object en Function.
82
OU
Leereenheid 7c JavaScript: Objecten en functies
OPDRACHTEN OPDRACHT 7c.1 Optionele parameter, bij 1.1
In deze opdracht voegt u in het blogvoorbeeld uit de vorige leereenheid de mogelijkheid toe om de links in de lijst met links alfabetisch te sorteren. Uitgangspunt vormen de bestanden blog/index.html, blog/blog.js en blog/blog.css. In de HTML-code voegt u in het formulier voor een link een checkbox toe waarmee de gebruiker aan kan geven dat de lijst met links (alfabetisch) gesorteerd moet worden. Verander de functie updateLijst zo dat deze overweg kan met een optionele parameter die aangeeft dat de lijst gesorteerd moet worden. Erachter komen of een checkbox wel of niet is aangevinkt kunt u met: document.getElementById("sorteer").checked
Ervan uitgaande dat het element met id sorteer een checkbox is, levert deze expressie een Boolean waarde op, die true is als de checkbox is aangevinkt. Een uitwerking vindt u in de bouwstenen, in blog/index.html en blog/blog.js. OPDRACHT 7c.2 De ( )-operator, bij 1.2
Voer in de console van Firebug in: var hallo = function() { alert("Hallo");};
Voer dan achtereenvolgens in: hallo; hallo();
Verklaar wat u ziet. Er is een toelichting bij de terugkoppeling. OPDRACHT 7c3 Datum bij entry, bij 1.2
In deze opdracht experimenteert u met een functie die als waarde aan een variabele is toegekend. Bij de bouwstenen vindt u blog/datum.html, blog/datum.css en blog/datum.js. Bekijk de code. Aan de HTML-code is een element toegevoegd waarin de datum zal worden vertoond. Aan het JavaScriptbestand is de variabele formatteerDatum uit paragraaf 1.2 toegevoegd, die, afhankelijk van de taal van de browser, een functie als waarde krijgt, die de huidige datum op de juiste manier geformatteerd teruggeeft. Het Datum-object is één van de standaard globale objecten van JavaScript. Verander de functie publiceer nu zo dat de datum bij een entry wordt gezet. Verander, wanneer de applicatie werkend is, de waarde van taal in "nl" en check of de datum juist wordt geformatteerd. U vindt een uitwerking bij de bouwstenen, in blog/datum.js.
OU
83
Webapplicaties: de clientkant
OPDRACHT 7c.4 Sort als parameter, bij 1.3 (facultatief)
var lijst = [1, 3, 9, 5]; lijst = lijst.sort(function(x, y) {return y - x;});
Voer bovenstaande code in het invoerveld van Firebug in, en verklaar het resultaat. U vindt een toelichting bij de terugkoppeling. OPDRACHT 7c.5 Functie als terugkeerwaarde, bij 1.4 (facultatief)
Bij de bouwstenen vindt u de bestanden tekst/tekst.html, tekst/tekst.css en tekst/tekst.js. Het is de bedoeling dat de tekst die u in de textarea invult, afhankelijk van de daarna geklikte knop omgeven wordt door strong-, em- of code-tags. De functie tagDit uit paragraaf 1.4 staat in het JavaScriptbestand. Maak van die functie gebruik om de variabelen italic, strong en code te voorzien van de waarde van de juiste functie, zoals in het voorbeeld van paragraaf 1.4. Bij de bouwstenen vindt u de uitwerking in tekst/tekst.js. Als u slim gebruik maakt van de id’s van de knoppen kunt u de JavaScript-code veel simpeler schrijven. Kunt u bedenken hoe? De uitwerking vindt u bij de bouwstenen in tekst/tekst-slim.js. NB: In een volgende leereenheid zult u leren hoe u in de event handler over informatie kunt beschikken over het element waarop is geklikt. Nu zit er nog even niets anders op dan voor elke knop een aparte event handler te schrijven. OPDRACHT 7c.6
Objecten in het blog, bij 2.1
In het blogvoorbeeld voegen we nu de mogelijkheid toe om elke entry van een titel te voorzien. Uitgangspunt is de code in blog/objecten.html en blog/objecten.js. Om de code voor het blogvoorbeeld overzichtelijk te houden is het mogelijk om de informatie voor blog-entries bij elkaar te zetten in een object: var blogEntry = { titel: "", tekst: "", datum: "" }
De functies regels en paragrafen hebben geen functie buiten de blogentries om. Het is daarom logisch om van die functies methoden van blogentry te maken. Verder kunt u blogentry nog een methode haalOp en een methode update geven, die respectievelijk de juiste gegevens uit de pagina ophalen en met die gegevens een nieuwe entry in de pagina zetten. De functie publiceer wordt dan heel kort: function publiceer() { blogEntry.haalOp(); blogEntry.update(); }
84
OU
Leereenheid 7c JavaScript: Objecten en functies
Het object blogentry ziet er als volgt uit: var blogEntry = { titel: "", tekst: "", datum: "", regels: function(eenTekst) {...}, paragrafen: function(eenTekst) {...}, haalOp: function() {...}, update: function() {...} }
Verander de code in objecten.js op deze manier, waarbij u de bodies van de methoden invult. Bij de bouwstenen vindt u de uitwerking in blog/objecten.js. OPDRACHT 7c.7 Cursuscatalogus, bij 2.1
In deze opdracht bekijkt u de [ ]-notatie bij objecten. Bij de bouwstenen vindt u de bestanden voor de cursuscatalogus: cursus/cursus.html, cursus/cursus.css en cursus/cursus.js plus een aantal afbeeldingen. Bekijk de code. De applicatie reageert op de change event: document.getElementById("keuze").onchange = laatZien;
Verderop in de cursus worden de events behandeld waarop u vanuit JavaScript kunt reageren. Deze event vindt plaats als de waarde van het select-element verandert. Kijk wat de applicatie doet in Firefox.
FIGUUR 7c.7
Een cursuscatalogus in HTML en JavaScript
Waar in de code wordt gebruik gemaakt van de [ ]-notatie bij een object? Wat gebeurt daar precies? OPDRACHT 7c.8 Constructor, bij 2.2
Uitgangspunt is de code van cursus.js uit opdracht 7c.7. Voeg aan de code een constructor Cursus toe, met een naam en een beschrijving als parameters. Gebruik die constructor bij het opbouwen van de cursuslijst. Kunt u een voordeel van deze opzet bedenken? De uitwerking zit als cursus/cursus-constructor.js bij de bouwstenen. Een toelichting vindt u in de terugkoppeling.
OU
85
Webapplicaties: de clientkant
OPDRACHT 7c.9 Number type en Number object, bij 2.3
Voer in de console van Firebug achtereenvolgens in: var nummerA = 1; var nummerB = 1; var nummerObjectA = new Number(2); var nummerObjectB = new Number(2); nummerA == nummerB; nummerObjectA == nummerObjectB; nummerObjectA.valueOf() == nummerObjectB.valueOf();
Verklaar de resultaten. U vindt een toelichting bij de terugkoppeling. OPDRACHT 7c.10 Werken met een prototype, bij 2.4
Uitgangspunt is de code uit opdracht 7c.8. a De informatie over een cursus wordt nu in de pagina gezet in de functie laatZien, met: document.getElementById("informatie").innerHTML = h2(cursus.naam) + div(img(cursuscode + ".jpg", cursuscode)) + div(p(cursus.beschrijving));
Dit codefragment hoort logisch gezien bij de cursusobjecten. Voeg de methode beeldAf, met de functionaliteit van dit codefragment, toe aan het prototype via de constructor. De uitwerking vindt u in de bouwstenen als cursus-prototype.js. b Wat is het voordeel van deze opzet boven de variant waarin u de methode aan de constructor zou hebben toegevoegd? U vindt een toelichting in de terugkoppeling. c Een nadeel van de huidige opzet van de cursuscatalogus is dat het tot lelijke resultaten leidt als we een cursus opnemen waarvoor nog geen omslag beschikbaar is. Een oplossing is om in de constructor een extra parameter mee te geven voor de omslag, en daar alleen van gebruik te maken als die extra parameter wordt meegegeven. Aan het prototype voegt u dan een property omslag toe, met een default-omslag voor cursussen waarvoor nog geen omslag bestaat. Gebruik het bestand onbekend.jpg als default-omslag, en breid de code uit. U vindt een uitwerking in cursus/cursus-defaultwaarde.js. Het bestand cursus/cursus-defaultwaarde.html bevat een extra cursus, zonder omslag. OPDRACHT 7c.11 Memory, bij de gehele leereenheid
86
In de bouwstenen vindt u de bestanden memory/memory.html, memory/memory.js en memory/memory.css. Als u memory.html in Firefox opent ziet u de volgende pagina:
OU
Leereenheid 7c JavaScript: Objecten en functies
FIGUUR 7c.8
De pagina memory.html
Het spel Memory bevat kaarten met afbeeldingen, en van elke afbeelding zijn er twee kaarten in het spel. De speler draait steeds twee kaarten om. Als ze dezelfde afbeelding bevatten haalt de speler ze uit het spel. Zo niet, dan worden ze weer dichtgedraaid en is de volgende speler aan de beurt. a Het is de bedoeling dat u stap voor stap een JavaScript-bestand schrijft waarmee u, uitgaande van deze pagina, een eenpersoons memory-spel bouwt. Bekijk de bestanden, met name het JavaScriptbestand. Welke objecten ziet u? Wat zou het voordeel kunnen zijn van een object waarin alles wat er met pagina-elementen moet gebeuren is geconcentreerd, zoals het gui-object in dit bestand? Welke constructors ziet u? U vindt een toelichting bij de terugkoppeling. b In de functie startSpel ziet u dat eerst het object gui in de speelmodus wordt gezet, en dat vervolgens de Kaart-objecten worden gecreëerd. Het aantal rijen en het aantal kolommen zijn beschikbaar in de vorm van de variabelen rijen en kolommen (er zijn meer afbeeldingen beschikbaar, om het aantal rijen of kolommen eventueel groter te maken). Om Kaart-objecten te creëren roept u de constructor aan met een nummer. Eén Kaart-object bevat de informatie voor twee kaarten met dezelfde afbeelding. Schrijf de functie maakKaarten zo dat de array kaarten gevuld wordt met voor elk nummer een Kaart-object, en dat de kaarten geschud zijn. Om de kaarten te kunnen schudden schrijft u ook de methode schudKaarten (met behulp van de meegeleverde functie shuffle). Een uitwerking vindt u bij de terugkoppeling.
OU
87
Webapplicaties: de clientkant
c
De volgende stap is het delen van de kaarten. Voor de methode
delen loopt u elke cel van de tabel in HTML af, en legt u daar een kaart neer. Dat ‘aflopen’ van de tabelcellen doet u via de id’s, die zo genummerd zijn dat u met twee for-lussen gemakkelijk de id’s voor alle cellen
kunt construeren. Het kiezen van de kaart, en het afhandelen van de administratie daaromheen, laat u over aan de methode kiesKaart. Ga er vanuit dat die methode u een Kaart object teruggeeft. Vraag vervolgens aan het gui-object om de afbeelding van de kaart aan de tabelcel toe te voegen. Schrijf de methode delen. U vindt een uitwerking in de terugkoppeling. d Het lastige werk, namelijk ervoor zorgen dat er van elke kaart twee in het spel terechtkomen, heeft u overgelaten aan de methode kiesKaart. Die gaat u nu implementeren. Daarvoor neemt u de eerste kaart uit de array kaarten, en u verlaagt het aantalExemplaren van die kaart. Wanneer dat aantal op nul uitkomt, moet de kaart uit de array kaarten gehaald worden: er zijn dan al twee van die kaarten neergelegd. Zo niet, dan moet de array kaarten opnieuw geschud worden, anders zou de volgende neer te leggen kaart de kaart met dezelfde afbeelding worden. Aan het object spel voegt u een property toe met als naam de id van de betreffende cel, en als waarde het Kaart-object dat in die cel terechtkomt. Daardoor krijgt het object spel een overzicht van welke kaarten in welke cellen terechtkomen. Schrijf de methode kiesKaart. Als het goed is zal de applicatie, als u herlaadt, nu de kaarten in geopende toestand laten zien. Een uitwerking vindt u in de terugkoppeling. e Bekijk de code van het object spel, en probeer de volgende vragen te beantwoorden: Wat doet de methode allesDicht, en waarom wordt de afbeelding van een dichte kaart niet getoond bij kaarten die geraden zijn? Wat doet de methode geraden? Wat doet de methode paarMatch? Wat doet de methode draaiKaart? Bij welke gebeurtenis zou deze methode moeten worden aangeroepen? Wat is de functie van de properties aantalOpen, idOpen en aantalGeraden? U vindt een toelichting in de terugkoppeling. f Om het spel werkend te krijgen moet nu in window.onload aan elke tabelcel een event handler worden gebonden, waarin de methode draaiKaart van het object spel wordt aangeroepen met de id van die tabelcel. Haal in de functie startSpel de commentaarstrepen weg voor de aanroep van allesDicht, en voeg in window.onload code toe om aan elke cel een event handler te binden. De functie die u bindt is:
88
OU
Leereenheid 7c JavaScript: Objecten en functies
function() { if (spel.aantalOpen < 2) { spel.draaiKaart(this.id); } }
U vindt een uitwerking in de toelichting, en bij de bouwstenen vindt u de hele applicatie, werkend.
ZELFTOETS
1
Gegeven is de volgende functie: function kwadraat(getal) { getal = parseInt(getal) || 0; return getal * getal; }
Wat is de waarde van de variabelen a en b na de volgende statements? var a = kwadraat(); var b = kwadraat;
Verklaar uw antwoord. 2
Is de uitspraak ‘De functie doSomething heeft één formele parameter, want als ik hem gebruik met één actuele parameter levert dat een waarde op.’ juist of onjuist? Verklaar uw antwoord.
3
Wat is een anonieme functie, en hoe kunt u een anonieme functie gebruiken?
4
Is een methode een speciaal soort property of is het geen property? Verklaar uw antwoord.
5
Wat is de dot-operator?
6
Schrijf een constructor Breuk, met parameters teller en noemer, die een object oplevert met properties teller en noemer.
7
Creëer met de constructor uit vraag 6 twee breuken half (met parameters 1 en 2) en derde (met parameters 1 en 3), en voeg aan het prototype van die breuken de methode print toe, die een string teruggeeft waarin de betreffende breuk geschreven wordt als teller/noemer.
OU
89
Webapplicaties: de clientkant
TERUGKOPPELING 1
Uitwerking van de opgaven
7c.1
Er is één formele parameter: woord. Er is één actuele parameter: "Open Universiteit".
7c.2
In het werkboek bent u op veel plaatsen code tegengekomen als: window.onload = function() { document.getElementById("vermenigvuldig").onclick = vermenigvuldig; };
Hier zijn onload en onclick variabelen (properties, in dit geval), en aan die variabelen wordt in beide gevallen als waarde een functie gegeven. 7c.3
Omdat er hier slechts een keuze tussen twee alternatieven gemaakt wordt, lijkt een if-else-constructie handiger dan een switch. Maar door een switch te gebruiken wordt het gemakkelijker om later de functie uit te breiden met meer talen.
7c.4
U bent de dot-operator al tegengekomen in code als: window.onload = function() { };
De variabele window heeft als waarde dus (een referentie naar) een object. 7c.5
Bij het uitvoeren van var getal = nummer.toString();
wordt de waarde van nummer (van type Number) eerst door impliciete typeconversie geconverteerd naar een waarde van type Number object. Dat object kent een methode toString, die wordt toegepast op die waarde. De methode toString levert een waarde van type String op; de variabele getal krijgt dus een waarde van type String.
90
7c.6
In Java hoort het type van een variabele bij die variabele. Er moet dus altijd bekend zijn wat dat type is, ook voordat de variabele een waarde heeft gekregen. Declareren van een variabele voordat hij wordt gebruikt is daarom noodzakelijk. In JavaScript hoort een type bij de waarde van een variabele. Het type kan mee veranderen met de waarde. Het declareren van een variabele heeft dus geen enkele zin: u kunt wel een type aangeven, maar er is geen enkele garantie dat de variabele dat type zal behouden.
7c.7
In Java kan een object alleen gecreëerd worden met behulp van een klasse: er kan een kloon van een ander object gecreëerd worden, maar ook dat object is gebonden aan een bepaalde klasse. In JavaScript kunt u met behulp van een literal een object creëren dat bij geen enkele constructor hoort.
OU
Leereenheid 7c JavaScript: Objecten en functies
2
Uitwerking van de opdrachten
7c.2
U heeft een variabele hallo gedefinieerd die als waarde een functie heeft. In de body van de functie wordt een alert gebruikt. Als u in de console van Firebug de naam van die variabele invoert, krijgt u de waarde van de variabele te zien: function(). Waneer u de naam, gevolgd door de ()-operator invoert, wordt de functie uitgevoerd: er verschijnt een alert.
7c.4
De functie die u aan sort meegeeft geeft een negatieve waarde als de eerste parameter voor de tweede moet komen, een positieve waarde als de eerste parameter na de tweede moet komen, en nul als de volgorde kan blijven zoals hij is. De expressie y-x geeft een positieve waarde als de tweede parameter groter is dan de eerste. De tweede parameter komt dus vóór de eerste als deze groter is. Het resultaat is dat de lijst van groot naar klein wordt gesorteerd.
7c.7
De plaats in de code waar gebruik wordt gemaakt van de [ ]-notatie bij objecten is de tweede regel van de functie laatZien: var cursus = cursuslijst[cursuscode];
De variabele cursuscode haalt een value op uit het volgende HTMLfragment: <select id="keuze">
De variabele cursuscode krijgt dus één van de waarden "T28131", "T25151", "T38111" of "T60122". De properties van het object cursuslijst heten precies zo. De waarde van de variabele cursuscode is dus altijd de naam van een van de properties van het object cursuslijst. De expressie cursuslijst[cursuscode] levert daarom de waarde van zo’n property op. Die waarde is een object, met als properties naam en beschrijving. 7c.8
Een voordeel van de opzet met een constructor is dat nu automatisch alle gecreëerde objecten dezelfde namen voor hun properties hebben. Als u elk object creëert met behulp van een literal kunnen er gemakkelijk foutjes insluipen wanneer u een typefout maakt bij een property.
7c.9
De code was: var nummerA = 1; var nummerB = 1; var nummerObjectA = new Number(2); var tekstObjectB = new Number(2); nummerA == nummerB; nummerObjectA == nummerObjectB; nummerObjectA.valueOf() == nummerObjectB.valueOf();
OU
91
Webapplicaties: de clientkant
De variabelen nummerA en nummerB hebben dezelfde waarden, de variabelen nummetObjectA en nummerObjectB niet: die bevatten beide een referentie, en elke referentie wijst naar een ander object. Met behulp van de methode valueOf kunt u de waarden van beide objecten met elkaar vergelijken; die zijn gelijk. 7c.10
b Het voordeel van het plaatsen van de methode in het prototype van de constructor is dat de methode nu maar één keer in het geheugen staat. Als de methode in de constructor zelf wordt gedefinieerd, wordt er voor elk Cursus-object een instantie van de methode gecreëerd. In de code van deze opdracht zijn dat dus vier instanties.
7c.11
a In de code van memory.js zijn spelKaarten, spel, gui en window objecten. Alle plaatsen in JavaScript-code waarin pagina-elementen geopend worden hebben een directe koppeling met de bijbehorende pagina: de namen van id’s bijvoorbeeld moeten kloppen met de namen van id’s in het HTML-bestand. Door dergelijke code in één object te stoppen is het gemakkelijk om bij een verandering in de HTML-pagina terug te vinden waar er code in het JavaScript-bestand moet worden veranderd: dat is alleen maar binnen het gui-object. Er is één constructor in de code: Kaart. b De methode schudKaarten schrijft u als volgt: schudKaarten: function() { shuffle(this.kaarten); }
De methode maakKaarten kunt u als volgt schrijven: maakKaarten: function() { for (var i = 0; i < (rijen * kolommen)/2; ++i) { this.kaarten.push(new Kaart(i)); } this.schudKaarten(); }
c
De methode delen kunt u als volgt implementeren:
delen: function() { for (var rij = 0; rij < rijen; ++rij) { for (var kolom = 0; kolom < kolommen; ++kolom) { var id = rij.toString() + kolom.toString(); var memoryKaart = this.legKaart(id); gui.plaatje(id, memoryKaart.afbeelding); } } }
92
OU
Leereenheid 7c JavaScript: Objecten en functies
d De methode kiesKaart kunt u als volgt implementeren: kiesKaart: function(id) { var kaart = this.kaarten[0]; spel[id] = kaart; kaart.aantalExemplaren--; if (!kaart.aantalExemplaren) { this.kaarten.shift(); } else { shuffle(this.kaarten); } return kaart; }
e De methode allesDicht dient om voor alle kaarten die nog in het spel zijn de ‘dicht’-afbeelding te tonen. Dat moet niet gebeuren bij kaarten die al geraden zijn: de plek van die kaarten moet leeg blijven. De methode geraden zet eerst de property geraden van de twee betrokken kaarten op true, zodat ze bij het uitvoeren van de methode allesDicht zullen worden overgeslagen. De meegegeven id’s zijn de namen van properties van het object spel: daar heeft de methode kiesKaart van het object spelKaarten voor gezorgd. Vervolgens wordt aan gui gevraagd om in de betreffende tabelcellen geen afbeelding meer te tonen. De methode paarMatch voert uit wat er gedaan moet worden als de gebruiker twee kaarten heeft omgedraaid met dezelfde afbeelding: de methode geraden wordt aangeroepen, aantalOpen wordt op 0 gezet (de open kaarten zijn nu uit het spel verdwenen), aantalGeraden wordt met 1 verhoogd, en er wordt bekeken of alle kaarten inmiddels geraden zijn. Zo ja, dan wordt er vuurwerk vertoond. De methode draaiKaart zorgt ervoor dat de kaart in de tabelcel met de id die als parameter is meegegeven de bijbehorende afbeelding laat zien, en hoogt aantalOpen met 1 op. Wanneer er nu twee kaarten open liggen vergelijkt draaiKaart de open kaart met de vorige. Dat gebeurt door de id van de tabelcel van de vorige te onthouden als waarde van celOpen, en de kaart die daar ligt te vergelijken met de zojuist opengedraaide kaart. De kaart die in een tabelcel ligt is te vinden doordat de id van de cel een property van het object spel is, en de waarde van die property is de kaart. Als de kaarten gelijk zijn, wordt paarMatch aangeroepen. Als de kaarten niet gelijk zijn wordt gui gevraagd om de ok-knop te tonen, zodat de gebruiker kan aangeven dat de kaarten weer mogen worden dichtgedraaid. De methode draaiKaart zou dus moeten worden aangeroepen op het moment dat de gebruiker op een kaart klikt.
OU
93
Webapplicaties: de clientkant
De property aantalOpen wordt gebruikt om bij te houden hoeveel kaarten de gebruiker heeft opengedraaid. Dat mogen er niet meer dan twee zijn. De property celOpen wordt gebruikt om de id van de tabelcel van de eerste opengedraaide kaart mee te onthouden. De property aantalGeraden wordt gebruikt om bij te houden hoeveel kaarten al zijn geraden: wanneer alle kaarten geraden zijn wordt gui gevraagd om de ‘winstand’ van het spel te vertonen. f
De functie window.onload ziet er nu als volgt uit:
De variabele a krijgt als waarde de returnwaarde van de functie kwadraat als die zonder parameters wordt aangeroepen. De terugkeerwaarde is in dat geval 0. De variabele b krijgt als waarde de functie kwadraat. In dit geval gaat het om een toekenning: de ( )-operator wordt niet op de functie kwadraat toegepast.
2
De uitspraak is onjuist. In de body van een functie kunnen default-waarden voor parameters worden gedefinieerd. Het feit dat een aanroep van de functie met één actuele parameter een waarde oplevert, betekent dus niet dat de functie niet meer dan één formele parameter kent. Bovendien kan het zo zijn dat de functie geen enkele formele parameter kent: te veel parameters bij een aanroep worden genegeerd.
3
Een anonieme functie is een functie zonder naam. Een voorbeeld van het gebruik ervan is: var kwadraat = function(getal) { return getal * getal; }