Hoe in JS een onclick like functie bouwen met 'this'

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 22-07-2024
Het probleem is als volgt.

Ik heb een serverside control gemaakt die html uitpoept.

Onderdeel daarvan is dat bepaalde elementen van dat control events moeten gaan afvuren.

Wanneer ik op iets klik wil ik dus een onclick event af laten gaan.
Wanneer ik op save klik bijvoorbeeld wil ik eerst een before_save() en dan een after_save() event triggeren.

Dit moet dan d.m.v. JS.

wanneer dan het moment is aangebroken dat zo'n event moet triggeren kijk ik of bepaalde elementen een attribuut 'before_save' hebben. Die attributen lees ik uit en doe dan (ja ranzig i know) een eval() op de value van dat attribuut.
Dit komt (kort door de bocht) neer op:
HTML:
1
2
3
<div class="componentX" before_save="mijnfunctie(this)">
  <div class="savebutton" onclick="savebutton_click()"></div>
</div>


JavaScript:
1
2
3
4
5
6
7
savebutton_click = function() {
  ...  
  if (node.attributes['before_save'] !== null && typeof node.attributes['before_save'] === "string") {
    eval(node.attributes['before_save'].value);
  }
  ...
}


nu wil ik echter dat ik in de methode "mijnfunctie()" ook het keyword 'this' kan gebruiken. Dit kan natuurlijk, maar deze is momenteel altijd 'window' omdat eval geen context heeft.

Nu ken ik bind en call en apply, maar krijg het niet voor elkaar om de juiste 'this' mee te geven, en eigenlijk denk ik dat het er op neem komt dat ik de text die ik nu 'evalueer' ook direct uitvoer. Ik wil hem eigenlijk alleen interpreteren en dan een apply() doen met daarin de juiste context meegegeven.

Hoe doet javascript dat zelf?

een onclick="alert(this)" werkt ook gewoon goed. Hoe bouw je DIE functionaliteit.
kort door de bocht is wat ik wil dat ik zelf events kan verzinnen, bijvoorbeeld:
HTML:
1
<p oncolorchange="alert(this.style.color)" >woei</p>

This message was sent on 100% recyclable electrons.


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-09 16:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

BasieP schreef op maandag 18 juni 2012 @ 15:55:
nu wil ik echter dat ik in de methode "mijnfunctie()" ook het keyword 'this' kan gebruiken. Dit kan natuurlijk, maar deze is momenteel altijd 'window' omdat eval geen context heeft.

Nu ken ik bind en call en apply, maar krijg het niet voor elkaar om de juiste 'this' mee te geven, en eigenlijk denk ik dat het er op neem komt dat ik de text die ik nu 'evalueer' ook direct uitvoer. Ik wil hem eigenlijk alleen interpreteren en dan een apply() doen met daarin de juiste context meegegeven.
Que? Bij mijn weten krijgt eval() gewoon de context van de caller. Daarom werkt dit ook:
JavaScript:
1
2
3
4
5
6
7
var o =
{
    i: 34,
    foo: function() { eval("alert(this.i);"); }
};

o.foo();

Je moet die eval gewoon wrappen in een functie, en op die functie vervolgens je context apply'en.

Overigens, ik been geen JS guru, maar is het niet zo dat het nogal per browser verschilt wat de 'this' is in het geval van een event?

.edit: ik zie inderdaad dat je Function.call() of Function.apply() niet op die manier direct kan toepassen op eval.
JavaScript:
1
2
3
4
5
6
var o = { i : 34 };

var str = "alert(this.i)"; // de string die we willen evalueren op 'o'

eval.call(o, str);  // geeft 'undefined'
(function() { eval(str); }).call(o); // geeft '34';

[ Voor 21% gewijzigd door .oisyn op 18-06-2012 16:22 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • crisp
  • Registratie: Februari 2000
  • Laatst online: 15:40

crisp

Devver

Pixelated

2 manieren:

met eval:

JavaScript:
1
2
3
4
5
6
7
8
9
if (node.attributes['before_save'] !== null && typeof node.attributes['before_save'] === "string")
{
    var tmp = function()
    {
        return eval(node.attributes['before_save'].value);
    }

    tmp.call(node);
} 


of met de Function constructor:

JavaScript:
1
2
3
4
if (node.attributes['before_save'] !== null && typeof node.attributes['before_save'] === "string")
{
    Function(node.attributes['before_save'].value).call(node);
} 


:)

Intentionally left blank


Acties:
  • 0 Henk 'm!

  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 22-07-2024
quote: .oisyn
...
Mmm

In mijn code nu is het inderdaad zo dat de eval in een methode staat die gewoon in een 'window' context draait (dus niet een functie in een functie)
Dat is sowieso niet netjes en het klinkt heel logisch dat dat mijn probleem op zou kunnen lossen. :)

verder had ik inderdaad getest met dingen als eval("alert(this)").call(pietje) maar dat deed inderdaad wat jij ook al aangeeft niet heel erg veel.


@crisp
Die function constructor was precies wat ik zocht. (ik wist alleen niet dat ie bestond)

Ik ga denk ik beide oplossingen implementeren. Ik zet die functie sowieso in een andere functie (het object) zodat hij in ieder geval een context heeft (namelijk het object) en mocht je dan een context meegeven speel ik die ook door.

tnx

[ Voor 33% gewijzigd door BasieP op 18-06-2012 16:42 ]

This message was sent on 100% recyclable electrons.


Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
BasieP schreef op maandag 18 juni 2012 @ 15:55:
Het probleem is als volgt.

Ik heb een serverside control gemaakt die html uitpoept.

Onderdeel daarvan is dat bepaalde elementen van dat control events moeten gaan afvuren.

Wanneer ik op iets klik wil ik dus een onclick event af laten gaan.
Wanneer ik op save klik bijvoorbeeld wil ik eerst een before_save() en dan een after_save() event triggeren.

Dit moet dan d.m.v. JS.

wanneer dan het moment is aangebroken dat zo'n event moet triggeren kijk ik of bepaalde elementen een attribuut 'before_save' hebben. Die attributen lees ik uit en doe dan (ja ranzig i know) een eval() op de value van dat attribuut.
Eval() is hier nog wel het minst ranzige aan. Smijt die troep die je gebrouwen hebt maar gerust weg en begin opnieuw, want wat je nu neergezet hebt zal je op de langere baan alleen maar ellende opleveren. Probeer de clientside logica eens als klasse/module/widget/etc. op te zetten, zodat je die in een DOMContentLoaded event gewoon kunt initialiseren. Maakt je code een stuk onderhoudbaar, een stuk sneller en een stuk meer future-proof dan proberen alles houtje-touwtje aan elkaar te plakken met dit excuus voor aspect-oriented programming.

Alle inline meuk incl. eval() vliegt er sowieso uit als je in de toekomst ooit Content Security Policies wilt gaan gebruiken. (En ja; om het risico op XSS te reduceren wil je dat in kunnen gaan zetten in de toekomst. Je huidige broddeloplossing is misschien al op een aantal manieren te exploiteren omdat je uitvoerbare code in simpele HTML attributen onder brengt.) Daarnaast neemt nu al eval() in EcmaScript 5 strict mode de draaiende functie scope niet meer mee over: je krijgt altijd een kaal, gesandboxed global object als scope. Als je dus volgens best practices in strict mode programmeert krijg je jouw idee sowieso niet werkend.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-09 16:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

R4gnax schreef op dinsdag 19 juni 2012 @ 00:15:
Daarnaast neemt nu al eval() in EcmaScript 5 strict mode de draaiende functie scope niet meer mee over: je krijgt altijd een kaal, gesandboxed global object als scope. Als je dus volgens best practices in strict mode programmeert krijg je jouw idee sowieso niet werkend.
JavaScript:
1
2
3
4
var o = { i : 34 }; 
var str = "alert(this.i)"; // de string die we willen evalueren op 'o' 

eval("(function() { " + str + "; })").call(o);

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 24-09 23:11
Waarom hang je die events niet cliënt side aan je controls?

Je HTML pagina:
HTML:
1
2
3
<div id="componentX">
  <div id="savebutton"></div>
</div>


Javascript met een onload
JavaScript:
1
2
3
4
5
6
7
8
9
10
function setBeforeSaveEvents(savebutton,beforesave) {
    document.getElementById(savebutton).onclick=function() {
        var waarde = document.getElementById(beforesave).value;
        //Doe iets met de waarde
    }
}
//Onderstaande code kan je eventueel laten genereren in een apart PHP (met javascript headers) bestand
window.onload=function() {
    setBeforeSaveEvents('saveButton','componentX');
}

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
.oisyn schreef op dinsdag 19 juni 2012 @ 00:29:
[...]


JavaScript:
1
2
3
4
var o = { i : 34 }; 
var str = "alert(this.i)"; // de string die we willen evalueren op 'o' 

eval("(function() { " + str + "; })").call(o);
I stand corrected.

Je kunt inderdaad nog steeds een functie definiëren, naar buiten sturen en dan uitvoeren (evt. onder een andere scope). Had recent nog ergens gelezen dat er nog een last-minute wijziging op stapel lag die dit niet meer toe zou staan via eval(), zodat enkel nog de Function constructor voor dit doeleinde geschikt zou zijn. Misschien nog niet doorgevoerd of misschien al weer laten varen omdat het niet haalbaar was om dan compatible te blijven met ES3's versie van eval().

Eval blijft iets smerigs en de dynamische optimalisatie & compilatie in moderne JS engines kan er ook nog steeds niet goed mee om gaan. Dus hoewel het nog wel werkt moet je je eigenlijk toch af vragen of je het nog wel wilt. Doorgaans is de Function constructor een stuk beter te behappen, wat zich dan uit in het feit dat die code doorgaans meerdere malen sneller is uit te voeren. (Echter sneuvelt ook die laatste als je CSPs gebruikt.)

Acties:
  • 0 Henk 'm!

  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 22-07-2024
R4gnax schreef op dinsdag 19 juni 2012 @ 00:15:
[...]


Eval() is hier nog wel het minst ranzige aan. Smijt die troep die je gebrouwen hebt maar gerust weg en begin opnieuw, want wat je nu neergezet hebt zal je op de langere baan alleen maar ellende opleveren. Probeer de clientside logica eens als klasse/module/widget/etc. op te zetten, zodat je die in een DOMContentLoaded event gewoon kunt initialiseren. Maakt je code een stuk onderhoudbaar, een stuk sneller en een stuk meer future-proof dan proberen alles houtje-touwtje aan elkaar te plakken met dit excuus voor aspect-oriented programming.
iets met mug en olifant.

bovendien is een aantal van jouw stellingen ook niet waar. 'oo' programmeren in js is een trukendoos en niet echt supported. Het is per definitie niet sneller maar juist trager.

Voor wat ik wil is het tevens niet onderhoudbaarder. Ik heb nu twee functies (een onload die het zaakje init en een methode die dus de Function constructor gebruikt om de text in de juiste context uit te voeren.

twee functies kan ik nog aan elke JS newbie uitleggen. Als ik alles wat jij mij wilt laten implementeren moet uitleggen wil die beste jongen twee keer zoveel verdienen en dat vind mijn baas niet leuk.

This message was sent on 100% recyclable electrons.


Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
BasieP schreef op dinsdag 19 juni 2012 @ 15:05:
[...]
bovendien is een aantal van jouw stellingen ook niet waar. 'oo' programmeren in js is een trukendoos en niet echt supported. Het is per definitie niet sneller maar juist trager.
Driemaal onzin.

De methodologie achter OOP is zelfs op verschillende wijzes heel goed te verwezelijken in JavaScript en het heeft met prototypal inheritance ook taal ondersteuning ingebouwd zitten:

JavaScript:
1
2
3
4
5
6
7
8
9
10
function BaseClass() { /* ... */ }

BaseClass.prototype = {}
BaseClass.prototype.methodA = function() { /* ... */ }
BaseClass.prototype.methodB = function() { /* ... */ }

function InheritedClass() { /* ... */ }

InheritedClass.prototype = new BaseClass();
InheritedClass.prototype.methodC = function() { /* ... */ }


Wat is daar mag ik vragen een trucendoos aan? Is de prototype chain je teveel gevraagd en heb je inheritance niet nodig; dan kun je altijd gewoon nog een prototype-vrije module schrijven:

JavaScript:
1
2
3
4
5
6
7
8
9
10
var MyModule = function() {

  /*... */

  return {
    methodA : function() { /* ... */ },
    methodB : function() { /* ... */ },
    methodC : function() { /* ... */ }
  }
};


En wat betreft performance: men gaat echt niet een Vector3 class voor een 3D engine bouwen met behulp van prototypes / classes als dat significant langzaam zou zijn.

Sterker nog, het is waarschijnlijk zelfs efficienter dan het alternatief. Een method gedefinieerd op een prototype wordt door alle instances gedeeld wat betekent dat er minder geheugen verbruikt wordt en de compiler slechts eenmaal de functie in kwestie hoeft te compileren en optimaliseren. Daarnaast wordt dezelfde functie vaker aangedaan, waardoor de compiler ook eerder geneigd zal zijn de code als 'hot' te zien en deze te gaan optimaliseren en door te compileren naar machine code.

Hoe dan ook, het is zeker sneller dan de DOM te moeten crawlen op basis van een attribute selector zoals "[before_save]" en daarna functies aan te gaan maken met eval(). Ik kan je eigenlijk wel garanderen dat beiden vele malen langer duren dan het parsen en instantiëren van een class of module.
BasieP schreef op dinsdag 19 juni 2012 @ 15:05:
Voor wat ik wil is het tevens niet onderhoudbaarder. Ik heb nu twee functies (een onload die het zaakje init en een methode die dus de Function constructor gebruikt om de text in de juiste context uit te voeren.
Je vergeet daar nog de meute inline attributen die door je HTML gestrooid staan. In plaats van zaken één keer te tikken moeten deze individueel allemaal op orde gehouden moeten worden. Je spreekt daarnaast vanuit inline event handlers en eval ook nog globaal gedefinieerde functies aan wat een risico introduceert op conflicterende namen, dus dikke kans dat als twee componenten voor het eerst tegelijk gebruikt gaan worden er ergens iets stuk gaat. (Dan mag je dus fijn al die individuele attributen gaan nalopen en bijwerken.)

Inline event handlers en via eval tot leven geroepen code wordt door debuggers in aparte runtime gegenereerde contexts gepresenteerd, waardoor het debuggen van de code wanneer deze breekt (en dat zal enkel een kwestie van tijd zijn) ook nog eens nodeloos gecompliceerd gemaakt wordt.

Dat noem je werkelijk onderhoudbaar?
BasieP schreef op dinsdag 19 juni 2012 @ 15:05:
twee functies kan ik nog aan elke JS newbie uitleggen. Als ik alles wat jij mij wilt laten implementeren moet uitleggen wil die beste jongen twee keer zoveel verdienen en dat vind mijn baas niet leuk.
Dan is het dus eerder een kwestie van voor een dubbeltje op de eerste rij willen zitten en dan dus maar de kwaliteit van de software omlaag trekken om aan het kennis niveau van de kleinste gemene deler te voldoen. Ben benieuwd of je baas het nog steeds met deze visie eens is als over een jaartje deze techniek breder ingezet is, de bende ontploft en je dan problemen op mag gaan lossen. Dit soort comprissen sluiten op code kwaliteit levert op de lange baan praktisch altijd meer kosten op dan het in eerste instantie bespaart.

Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 24-09 23:11
R4gnax schreef op dinsdag 19 juni 2012 @ 19:54:
De methodologie achter OOP is zelfs op verschillende wijzes heel goed te verwezelijken in JavaScript en het heeft met prototypal inheritance ook taal ondersteuning ingebouwd zitten:

JavaScript:
1
2
3
4
5
6
7
8
9
10
function BaseClass() { /* ... */ }

BaseClass.prototype = {}
BaseClass.prototype.methodA = function() { /* ... */ }
BaseClass.prototype.methodB = function() { /* ... */ }

function InheritedClass() { /* ... */ }

InheritedClass.prototype = new BaseClass();
InheritedClass.prototype.methodC = function() { /* ... */ }


Wat is daar mag ik vragen een trucendoos aan? Is de prototype chain je teveel gevraagd en heb je inheritance niet nodig; dan kun je altijd gewoon nog een prototype-vrije module schrijven:
Ik doe het zelf zo:
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
Function.prototype.extendsAttributes=function(Super) 
    {
    var Self = this;

    var Func = function() 
        {
        Super.apply(this, arguments);
        Self.apply(this, arguments);
        };      
        
    Func.prototype = new Super();   
    Func.prototype.constructor = Self;

    return Func;
    }

Function.prototype.extendsFrom=function() 
    {   
    var oThis = this;
    var aProtos = new Array(arguments.length);

    for(var i=0;i<arguments.length;i++) 
        {   
        aProtos[i] = arguments[i];
        }
        
    var self = this;

    for(var i=0;i<aProtos.length;i++)
        {
        self = self.extendsAttributes(aProtos[i]);  
        }
        
    for(var i=0;i<aProtos.length;i++)
        {
        for(var x in aProtos[i].prototype) 
            {
            if((typeof this[x] == 'function' && typeof aProtos[i].prototype[x] == 'function' && aProtos[i].prototype[x] == this[x]) || typeof this[x] != 'function')
                {
                self.prototype[x] = aProtos[i].prototype[x];
                }
            }
        }
        
    return self;
    }

System.Windows.Forms.Button=function()
    {
    this._sObject = 'System.Windows.Forms.Button';
    this._sFilename = 'System.Windows.Forms.Button.js';
    
    this._sParent = null;
    }

System.Windows.Forms.Button = System.Windows.Forms.Button.extendsFrom(System.Windows.Forms.Controls);


Dit is bijv. een stukje code waarin een zelfgemaakte Button control een extensie is van een standaard object Control. Zo heb ik verschillende object die elkaar weer overerven. Niks raars aan :)

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 22-07-2024
@R4gnax
prachtig verhaal waar ik het redelijk mee eens ben, maar je antwoord niet op mijn stelling.

twee losse functies defineren en die aanroepen is per definitie sneller dan een object genereren en een memberfunctie van een object aanroepen.
Dat was de stelling.

This message was sent on 100% recyclable electrons.


Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
BasieP schreef op woensdag 20 juni 2012 @ 07:54:
twee losse functies defineren en die aanroepen is per definitie sneller dan een object genereren en een memberfunctie van een object aanroepen.
Ten eerste: dat hoeft helemaal niet zo te zijn. Dat zou je nog behoorlijk kunnen verbazen.

Ten tweede: Je gaat er nogmaals compleet aan voorbij dat je met zo een methode wel wat meer moet doen dan enkel "twee losse functies defineren (sic) en die aanroepen": Je moet DOM nodes op gaan zoeken (ook querySelectorAll is nog steeds een paar milliseconden werk op een groot document), daarvan attributen lezen (getAttribute is onder IE behoorlijk duur) en de gelezen waarde daarna nog via eval() tot draaibare code verheffen.
BasieP schreef op woensdag 20 juni 2012 @ 07:54:
maar je antwoord niet op mijn stelling.
En ten derde:
R4gnax schreef op dinsdag 19 juni 2012 @ 19:54:
Hoe dan ook, het is zeker sneller dan de DOM te moeten crawlen op basis van een attribute selector zoals "[before_save]" en daarna functies aan te gaan maken met eval(). Ik kan je eigenlijk wel garanderen dat beiden vele malen langer duren dan het parsen en instantiëren van een class of module.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-09 16:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

R4gnax schreef op dinsdag 19 juni 2012 @ 19:54:
Sterker nog, het is waarschijnlijk zelfs efficienter dan het alternatief. Een method gedefinieerd op een prototype wordt door alle instances gedeeld wat betekent dat er minder geheugen verbruikt wordt en de compiler slechts eenmaal de functie in kwestie hoeft te compileren en optimaliseren.

Daarnaast wordt dezelfde functie vaker aangedaan, waardoor de compiler ook eerder geneigd zal zijn de code als 'hot' te zien en deze te gaan optimaliseren en door te compileren naar machine code.
En hoe denk je dat dat is als elke instance zijn eigen method reference heeft? Hint: ik noemde het al, een method reference. De code wordt echt niet geduplicate. Of je een method nou aan de prototype of aan iedere instance hangt, er is en blijft maar één method die gecompiled en geoptimized dient te worden en die contenu aangedaan wordt. Hij is dus net zo 'hot'.

Het verschil in performance zal vooral komen door de extra laag van indirectie enerzijds (de lookups worden eerst op de instance gedaan, daarna pas op de prototype - maar een optimizer zal hier wel raad mee weten) en het kleiner worden van de instances anderzijds (een instance heeft minder members als de methods op de prototype zitten, zoals je al aangaf)

[ Voor 15% gewijzigd door .oisyn op 20-06-2012 11:28 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
.oisyn schreef op woensdag 20 juni 2012 @ 11:25:
[...]

En hoe denk je dat dat is als elke instance zijn eigen method reference heeft? Hint: ik noemde het al, een method reference. De code wordt echt niet geduplicate. Of je een method nou aan de prototype of aan iedere instance hangt, er is en blijft maar één method die gecompiled en geoptimized dient te worden en die contenu aangedaan wordt. Hij is dus net zo 'hot'.
Als je een module pattern gebruikt dat op het einde elke keer een set anonieme functies in een object literal gooit en die literal terug geeft als return waarde, dan zijn het niet referenties naar dezelfde functie, maar echt elke keer nieuwe Function instanties. Het zal wss. afhangen van de implementatie v/d JS engine of de geparste code voor de function bodies herbruikt wordt.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-09 16:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

Natuurlijk zijn de instances waar je vanuit javascript mee communiceert anders - ze kennen verschillende contexts. Maar de code is gewoon hetzelfde, die wordt echt niet gedupliceerd.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.

Pagina: 1