[Javascript] Private variabelen en Object.create

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Amras
  • Registratie: Januari 2003
  • Laatst online: 14:55
Met Javascript is het mogelijk om private variabelen te simuleren door middel van scoping. Dit is als volgt te implementeren:

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
var Foo = (function () {

    var bar;

    return {
        getBar: function () {
            return bar;
        },
        setBar: function (b) {
            bar = b;
        }
    };
})();

In dit geval is de variabele bar in een Foo object alleen benaderbaar via de getBar en setBar methoden. Ik gebruik dit regelmatig en het werkt goed voor singletons. Als ik echter meerdere instanties van Foo maak, dan wordt de bar variabele helaas gedeeld en dat levert ongewenst gedrag op. Onderstaande code illustreert dit:
JavaScript:
1
2
3
4
5
6
7
8
9
10
var foo1 = Object.create(Foo);
foo1.setBar(3);

alert(foo1.getBar()); // alerts '3', OK

var foo2 = Object.create(Foo);
foo2.setBar(4);

alert(foo2.getBar()); // alerts '4', OK
alert(foo1.getBar()); // alerts '4', NOT OK!

Zoals de comments aangeven verwacht ik bij de laatste alert uiteraard een 3 i.p.v. 4.

Nu kan ik op internet voorbeelden vinden van het creëren van meerdere instanties door middel van Object.create waarbij de methoden netjes op het prototype gedefinieerd zijn (bijv. deze), maar ik zou graag variabelen afschermen in sommige van mijn objecten.

Heeft iemand een idee hoe ik variabelen afschermen voor prototypes waar meerdere instanties van worden aangemaakt, zonder dat de waarden van deze variabelen gedeeld worden tussen de instanties?

Acties:
  • 0 Henk 'm!

  • storeman
  • Registratie: April 2004
  • Laatst online: 13:59
Je schrijft naar een globale "bar" variabele, deze zit niet binnen de scope van het object. Dit werkt zoals jij bedoeld:

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var Foo = {
        bar: null,
        getBar: function () {
            return this.bar;
        },
        setBar: function (b) {
            this.bar = b;
        }
};

var foo1 = Object.create(Foo);
foo1.setBar(3);

console.log(foo1.getBar()); // alerts '3', OK

var foo2 = Object.create(Foo);
foo2.setBar(4);

console.log(foo2.getBar()); // alerts '4', OK
console.log(foo1.getBar()); // alerts '4', NOT OK!


Die extra functie eromheen snap ik ook niet helemaal. Dit voegt mijn inziens helemaal niets toe.

[ Voor 7% gewijzigd door storeman op 13-03-2012 11:08 ]

"Chaos kan niet uit de hand lopen"


Acties:
  • 0 Henk 'm!

  • Priet
  • Registratie: Januari 2001
  • Nu online

Priet

To boldly do what no one has..

storeman schreef op dinsdag 13 maart 2012 @ 11:06:
Die extra functie eromheen snap ik ook niet helemaal. Dit voegt mijn inziens helemaal niets toe.
Dat zorgt er juist voor dat de variabele bar private wordt. Van buitenaf is dan alleen benaderbaar wat in de return teruggegeven wordt.

"If you see a light at the end of a wormhole, it's probably a photon torpedo!"


Acties:
  • 0 Henk 'm!

  • Amras
  • Registratie: Januari 2003
  • Laatst online: 14:55
Priet schreef op dinsdag 13 maart 2012 @ 11:10:
[...]

Dat zorgt er juist voor dat de variabele bar private wordt. Van buitenaf is dan alleen benaderbaar wat in de return teruggegeven wordt.
Dat klopt inderdaad, het voorstel van storeman doet functioneel wel wat het moet doen, maar de bar variabele is nu gewoon benaderbaar door foo.bar op te vragen. Door middel van scoping probeer ik dat dus te voorkomen. :)

Acties:
  • 0 Henk 'm!

  • Flowmo
  • Registratie: November 2002
  • Laatst online: 18-08 08:24
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
var Foo = function () {
 
    var bar;
 
    return {
        getBar: function () {
            return bar;
        },
        setBar: function (b) {
            bar = b;
        }
    };
};

var foo1 = new Foo();
foo1.setBar(3);
 
alert(foo1.getBar()); // alerts '3', OK 
 
var foo2 = new Foo();
foo2.setBar(4);
 
alert(foo2.getBar()); // alerts '4', OK 
alert(foo1.getBar()); // alerts '3', OK!


je maakt gebruik van een IIFE (iffy, google it :)), maar dat moet dus niet als je nieuwe instanties van dat object wil maken. De haakjes die om de function() moeten dus weg, anders roep je de functie meteen aan. Dan is Foo een verwijzing naar die specifieke functie, ipv een nieuwe instantie van die functie. Ik maak hier ook gebruik van 'new' ipv Object.create(). Verder doet het nu wat je wilt geloof ik.

Edit: zie http://jsfiddle.net/RGFjs/

[ Voor 13% gewijzigd door Flowmo op 13-03-2012 11:19 ]


Acties:
  • 0 Henk 'm!

  • Amras
  • Registratie: Januari 2003
  • Laatst online: 14:55
Dat lijkt inderdaad goed te werken. Bedankt ook voor je zoekterm, dat levert goede resultaten op.

In jouw oplossing lijkt het echter of de functies niet op het prototype gedefinieerd zijn, maar op de instanties zelf. Dit resulteert in onnodig extra geheugengebruik. Neem het volgende voorbeeld:

JavaScript:
1
2
3
4
5
6
7
8
function Bar() {}

Bar.prototype.test = function () { alert('bar'); };
var bar = new Bar();
bar.test(); // alerts 'bar'

Bar.prototype.test = function () { alert('test'); };
bar.test(); // alerts 'test'

De functiedefinities van Bar zitten op zijn prototype, waardoor deze slechts eenmaal in het geheugen staan. Bovenstaand voorbeeld toont dit aan door de test functie op het prototype te vervangen. Als ik dit echter probeer in jouw oplossing, dan lijken de functies op de instanties zelf te zitten.

Opzich hoeft dit niet heel erg te zijn, maar als je echt veel instanties gaat aanmaken dan zal het geheugen-gebruik flink toenemen. Ik heb dit zelf geprobeerd als volgt te bewerkstelligen, maar dat levert helaas niet het gewenste resultaat op.
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var Foo = new (function () {
    var bar;
    return {
        getBar: function () {
            return bar;
        },
        setBar: function (b) {
            bar = b;
        }
    };
});

var foo1 = Object.create(Foo);
foo1.setBar(3);

alert(foo1.getBar()); // alerts '3', OK

var foo2 = Object.create(Foo);
foo2.setBar(4);

alert(foo2.getBar()); // alerts '4', OK
alert(foo1.getBar()); // alerts '4', NOT OK!

Helaas levert dit weer een 4 op als derde alert en ook hier lijken de functies niet op het prototype te zitten. Faalpoging dus. Ik zie door de scopes, constructors en prototypes even de oplossing niet meer. :P

Acties:
  • 0 Henk 'm!

  • Flowmo
  • Registratie: November 2002
  • Laatst online: 18-08 08:24
Daar heb je wel gelijk in, maar dan kan je het als volgt oplossen. Dan definieer je de functie Foo() en pas je de prototype daar van aan. Elke nieuwe instantie van Foo() erft dan de functies uit de prototype. Dat is toch wat je bedoelt en wilt?

Zie code:
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var Foo = function() {};

Foo.prototype = {
    bar: null,
    getBar: function() {
     alert(this.bar);
    },
    setBar: function(number) {
     this.bar = number;
    }                
}
 
var foo1 = new Foo();
foo1.setBar(3);
foo1.getBar(); // Alert 3, OK

var foo2 = new Foo();
foo2.setBar(4);

foo2.getBar(); // Alert 4, OK
foo1.getBar(); // Alert 3, OK

Acties:
  • 0 Henk 'm!

  • Amras
  • Registratie: Januari 2003
  • Laatst online: 14:55
Bijna dus, want nu is de bar variabele weer publiek beschikbaar.

Ik begin mij steeds meer af te vragen of het überhaupt wel mogelijk is, want ik wil functies vooraf definieren in het prototype die werken op een variabele die pas in de instanties aangemaakt wordt. Misschien moet ik maar wat minder puristisch worden en accepteren dat het gewoon niet gaat lukken om men variabelen private te maken.

Acties:
  • 0 Henk 'm!

  • storeman
  • Registratie: April 2004
  • Laatst online: 13:59
Hoewel ik niet precies snap wat je nu probeert op te lossen, javascript is niet echt hiervoor bedoeld, is dit volgens mij wat je wilt:

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
var Foo = (function () {

    var bar = 0;

    return {
        getBar: function () {
            return bar;
        },
        setBar: function (b) {
            bar = b;
        }
    };
});

var foo1 = new Foo();
foo1.setBar(3);

console.log(foo1.getBar()); // alerts '3', OK

var foo2 = new Foo();
foo2.setBar(4);

console.log(foo2.getBar()); // alerts '4', OK
console.log(foo1.getBar()); // alerts '3', OK
console.log( foo1.bar ); // Undefined, OK
console.log( Foo.bar ); // Undefined, OK
console.log( Foo().bar ); // Undefined, OK

"Chaos kan niet uit de hand lopen"


Acties:
  • 0 Henk 'm!

Verwijderd

Amras schreef op dinsdag 13 maart 2012 @ 14:14:
Misschien moet ik maar wat minder puristisch worden en accepteren dat het gewoon niet gaat lukken om men variabelen private te maken.
Misschien moet je accepteren dat het geen probleem is om je methodes op elke instantie te hebben. Geheugengebruik is echt zo'n ramp niet.

Acties:
  • 0 Henk 'm!

Verwijderd

Zo denk ik er zelf over:
  • Pseudoclassical (constructor function, prototype property en new): straal negeren in eigen code;
  • Prototypal (object literals en create): gebruiken wanneer alles publiek mag zijn;
  • Functional: the way to go.
Overcompleet voorbeeld:
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
57
58
59
60
61
62
63
64
65
/*jslint devel: true*/

//myApp namespace
var myApp = (function () {

    "use strict";

    //foo class
    var foo = function (bar) {

            //private/protected
            var instance = {},

                getBar = function () {

                    return bar;
                },

                setBar = function (b) {

                    bar = b;
                },

                initialise = function () {

                    bar = bar || 0;
                };

            //public
            instance.getBar = getBar;
            instance.setBar = setBar;

            initialise();

            return instance;
        },

    //app class
        app = function () {

            //private/protected
            var foo1 = foo(),
                foo2 = foo(1),

                main = function () {

                    alert("foo1: " + foo1.getBar()); //0
                    alert("foo2: " + foo2.getBar()); //1
                    foo1.setBar(2);
                    foo2.setBar(3);
                    alert("foo1: " + foo1.getBar()); //2
                    alert("foo2: " + foo2.getBar()); //3
                },

                initialise = function () {

                    main();
                };

            initialise();
        };

    //app start
    app();
}());

Merk op dat foo "durable" is. Je kan aan dit patroon ook zeer eenvoudig overerving toevoegen. Verder sluit ik mij aan bij Bleus. Ik zou niet wakker liggen van het "extra" geheugengebruik.

Acties:
  • 0 Henk 'm!

  • Amras
  • Registratie: Januari 2003
  • Laatst online: 14:55
Dank voor je voorbeeld, ik zal het eens nader bestuderen en eruit halen waar ik enthousiast van word. :)

De opmerking over het geheugengebruik is deels terecht. In een 'normale' site zal je weinig merken van die paar extra functies, maar ik ben momenteel een beetje aan het klooien met HTML5 en Canvas en probeer een game in elkaar te flansen. In dat soort toepassingen lijkt mij het geheugengebruik weer wel een issue, al is het in het huidige stadium niet echt een probleem. Daarbij, mocht het lukken om een oplossing te verzinnen waarin aan beide eisen voldaan wordt, dan heeft dat natuurlijk altijd de voorkeur. Vooralsnog worden het dan maar private variabelen en functies op de instances.

Acties:
  • 0 Henk 'm!

Verwijderd

Je hebt zeker een punt, dat ga ik niet ontkennen. Echter, mijn ervaring leert me dat geheugengebruik niet je grootste probleem zal zijn (je draw call kort houden daarentegen). Als ik zou moet kiezen tussen "lager" geheugengebruik of private variabelen zou ik niet twijfelen en voor private variabelen gaan.

Niet echt gerelateerd aan je vraag maar potentieel interessant: RequireJS en meer bepaald in combinatie met het CommonJS Module format.
Maakt je project meer beheersbaar en kan je je code in praktisch elke JavaScript omgeving hergebruiken (ideaal om bijvoorbeeld met behulp van Node.js anti-cheat functionaliteit te implementeren).
Pagina: 1