Hey allesammen!
Jeg sad her forleden og legede lidt med tanken om private members i JavaScript, det var langt fra første gang jeg tænkte på om det var muligt, og det er nok også en af de få ting jeg virkelig ægrer mig over i JavaScript.
I C++ fx, er det bare at skrive
private foran din member, og vupti så er den på plads, og kan bruges af de metoder der nu en gang har adgang til denne member. I JavaScript er dette jo nogenlunde muligt, I kender sikkert den constructor-baserede tilgang til private members i en klasse:
- function Person(name, secret) {
- this.name = name;
-
- this.tellSecret = function() {
- return secret;
- }
- }
-
- var john = new Person("John", "Privacy");
-
- >> john.tellSecret();
- "Privacy"
- >> john.secret;
- undefined
Det er altsammen meget godt, men det ødelægger lidt noget af det allerbedste ved JavaScript, Prototypal-Based OOP. Muligheden for at udvide klasserne med funktioner senere hen, MED adgang til private members er altså her ikke muligt. Man ville være nød til at lave "semi-private" members, ala:
- function Person(name, secret) {
- this.name = name;
- this._secret = secret;
- }
-
- Person.prototype.tellSecret = function() {
- return this._secret;
- }
-
- var john = new Person("John", "Privacy");
-
- >> john.tellSecret();
- "Privacy"
- >> john._secret;
- "Privacy"
Det virker altså meget godt, men idéen med private members er lidt død, for de er jo ikke "rigtigt" private.
Jeg ægrede mig i lang tid over dette, indtil jeg for nyligt fandt på en løsningsmodel! Endda en "lovlig" een, uden brug af Function.source, eval eller andre bøller! Funktionen jeg har fundet på har jeg kaldt for
Function.Privacy. Normalt er jeg ikke tilhænger af stort begyndelsesbogstav i funktionnavne, men da denne funktion fungere som en konstruktør, syntes jeg at det var passende. Function.Privacy, lyder:
- Function.prototype.privacy = function() {
- function merge(obj1, obj2){
- var obj3 = {}, attrname;
- for (attrname in obj1) { obj3[attrname] = obj1[attrname]; }
- for (attrname in obj2) { obj3[attrname] = obj2[attrname]; }
- return obj3;
- }
-
- var env = {};
- var obj = new (this.bind.apply(this, [].concat.apply([null], arguments)))();
- env.private = obj.private || {};
- delete obj.private;
-
- for(var i in obj) {
- if(typeof obj[i] === "function") obj[i] = obj[i].bind(merge(obj, env));
- }
-
- return obj;
- }
Først opretter jeg en objekt-variabel, env(ironment), som bruges til at holde de private variabler der defineres/ændres.
Herefter opretter jeg selve objektet (klassen), ved at kalde dens konstruktør, som i dette tilfælde holdes af variablen
this. For at kunne kalde konstruktøren med diverse argumenter, benytter jeg mig at et "trick" til at konvertere
arguments variablen, til et "ægte" array.
Jeg overfører det det private object der er blevet oprettet i konstruktøren, hvis det ikke er blevet lavet, bliver "private" variablen bar til et tomt object.
Til sidst, gennemløber jeg samtlige funktioner, objektet har, og benytter ECMA 5 funktionen Function.bind, til at "binde" scopet til objektet der returneres af merge metoden, mellem objektets egne public members, og "environments" private members.
Jeg vil yderst gerne høre feedback på denne metode fra jer andre, omkring brugbarhed, forbedringer og diverse kommentarer!
Jeg har allerede performance testet denne metode, og den klare sig selvfølgelig ikke nær så godt som de "normale" OOP metoder, men den er alligevel oppe på ca. 350.000 ops/sec på JSPerf.com
Det er klart at denne metode ikke skal bruges til at oprette en million objekter med, men som en POC eller til mindre projekter med færre kodelinjer, kunne jeg forestille mig denne funktion, som behagelig.
Lad mig høre hvad i tænker!
// Saebekassebil