ok, here goes
extend() is een functie bedoelt om de eigenschappen van het ene object over te kopieren naar een ander object. Let's disect
JavaScript:
1
| Object.extend = function(dest, source, allowOverwrite) |
We maken de functie aan als een eigenschap van het primitive Object object, dat heeft eigenlijk maar 1 reden: geen global scope vervuiling. Deze functie heeft 3 argumenten:
'dest' staat voor 'destination' oftwel het object waarnaartoe gekopieerd moet worden
'source' is het source object
'allowOverwrite' is een switch waarmee je kan bepalen of eventueel al bestaande eigenschappen overschreven mogen worden of niet.
Op naar de body:
for-in is een taalconstructie waarmee je itereert over alle eigenschappen van een object. Uiteraard willen we alle eigenschappen weten van ons source object.
JavaScript:
1
| if (source.hasOwnProperty(prop) && (allowOverwrite || !dest.hasOwnProperty(prop))) |
nu wordt het al wat lastiger. Aangezien for-in ook itereert over prototyped eigenschappen moeten we kijken of de eigenschap wel een 'eigen' eigenschap van het source object is, daar gebruik je hasOwnProperty voor. Uiteraard kijken we ook meteen even of ons doel object wellicht al over deze eigenschap beschikt wanneer allowOverwrite een boolean false oplevert.
JavaScript:
1
| dest[prop] = source[prop]; |
Als aan de voorwaarden voldaan is mag de eigenschap gekopieerd worden,
en we retourneren uiteindelijk het uitgebreide doel object. Meestal doen we echter niets met het resultaat, objecten worden in javascript altijd by reference doorgegeven en niet by copy, dus je krijgt geen nieuw object.
Nu dat we deze handige functie hebben gemaakt moeten we 'm ook gaan gebruiken, en dat doen we om aan het Function primitive object een prototyped method toe te kennen. Pseudo-isch:
JavaScript:
1
| Object.extend(Function.prototype, newProperties); |
waarbij newProperties een object is. In dit geval geef ik meteen een object literal mee waarvan dit de gebruikelijke syntax is:
JavaScript:
1
2
3
4
5
| {
property1: propertyValue1,
property2: propertyValue2,
"#(*&$%^": propertyValue3
} |
(bij speciale karakters moet de eigenschapnaam tussen quotes maar die zijn dus niet altijd verplicht)
Nu het interessante deel: de functie bind()
Dat is raar, geen argumenten? Toch wel; argumenten hoef je in javascript niet exact te benoemen, binnen de functie heb je de beschikking over de een arguments object wat een soort array-achtig object is welke de meegegeven argumenten in volgorde bevat. Kortom: je kan een wisselend aantal argumenten meegeven, en dat is leuk
Remember wat ik eerder zei over het gebruik van bind(): functionRef.bind(scopeObject, parm1, parm2, parm3, etcetera);
the hard part:
We hebben bind() aangemaakt als een prototyped method van het primitive Function object. Dat wil zeggen dat elke functie instance deze method overerft. Door deze method aan te roepen op een functie instance zal bind() in de scope van die functie instance worden uitgevoerd, dus 'this' verwijst automagisch naar de functie die we willen toekennen
JavaScript:
1
| args = [].slice.call(arguments, 0), |
auch! WTF? Wat zei ik ook alweer over het arguments object? Juist: dat het een soort array-achtig object is. Punt is dat het geen
instance van Array is en we dus niet zondermeer array-methods kunnen loslaten op dat object. Da's jammer, want ik wil wel graag een echte array hebben om straks aan apply() mee te kunnen geven, en daarbij wil ik het eerste argument, mijn scopeObject, uit die array halen.
Maar waar we hier mee bezig zijn, het uitvoeren van een functie in een specifieke scope, kunnen we ook doen met native methods van een object... of een array. In het kort: ook al is arguments geen array, ik kan wel de array slice method uitvoeren op het arguments object door te zorgen dat het arguments object de scope heeft.
Dus men heeft nodig: een referentie naar de slice method hetgeen het makkelijkst is door gewoon een array literal te nemen en te verwijzen naar de slice method: [].slice
Dat is dus een functie instance, en daar kunnen we weer de call() of apply() method op loslaten waarbij het eerste argument het scopeObject (ons arguments object) is en de rest een bunch argumenten. We gebruiken call dus die argumenten kunnen komma gescheiden worden toegevoegd (bij apply moet dat een array zijn). We gebruiken maar 1 argument voor slice() namelijk het begin element, en we beginnen bij het begin dus 0.
Het uiteindelijke resultaat: de inhoud van ons arguments object maar nu in een echte array \o/
En nu is het dus ook kinderlijk eenvoudig om het eerste argument, ons scopeObject, uit die array te trekken: namelijk gewoon shiften
Nu hebben we alles wat noodzakelijk is om een functie te genereren die onze functie instance uit kan voeren in de scope van het door ons opgegeven object en met de eventuele argumenten:
JavaScript:
1
2
3
4
| return function()
{
return handler.apply(object, args.concat([].slice.call(arguments, 0)));
} |
Hej, waarom doe je hier weer dingen met arguments? Wel, imagine een eventhandler, wat geeft die doorgaans als argument mee aan de handler? Juist, een event object

Die willen we dus nog toevoegen aan de uiteindelijk functiecall, dus pas ik de hele arguments-truuk nog een keer toe zodat ik uiteindelijk 2 arrays heb (de door jezelf opgegeven argumenten, en de extra argumenten zoals het event object) en die plak ik dmv concat() aan elkaar.
Uiteindelijk gebruik ik hier niet call maar apply welke de argumenten in arrayvorm accepteerd - heel handig
Dan nu nog een voorbeeldje met een eventhandler waarbij ik een variabel argument wil meegeven. Stel je dit voor:
JavaScript:
1
2
3
4
5
6
7
| for (var i = 0, div; i < 10; i++)
{
div = document.createElement('div');
div.appendChild(document.createTextNode(i));
div.onclick = function() { alert(i); }
document.body.appendChild(div);
} |
Je krijgt nu 10 divjes, maar op welke ik ook klik zet alerten allemaal '10'. Dat komt omdat de variabele i in de anonieme functie een referentie is naar de i in de onderliggende scope, en die staat na het completeren van de loop op 10. Het voordeel van bind() is dat het een extra scope genereerd, dus ik kan eenvoudig dit doen:
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
| function handler(i)
{
alert(i);
}
for (var i = 0, div; i < 10; i++)
{
div = document.createElement('div');
div.appendChild(document.createTextNode(i));
div.onclick = handler.bind(div, i);
document.body.appendChild(div);
} |