Dostęp do zmiennych prywatnych członków z funkcji zdefiniowanych w prototypie

187

Czy jest jakiś sposób na udostępnienie zmiennych „prywatnych” (zdefiniowanych w konstruktorze) metodom zdefiniowanym w prototypie?

TestClass = function(){
    var privateField = "hello";
    this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};

To działa:

t.nonProtoHello()

Ale to nie:

t.prototypeHello()

Jestem przyzwyczajony do definiowania moich metod w konstruktorze, ale odchodzę od tego z kilku powodów.

kody morganowe
źródło
14
@ ecampver, z wyjątkiem tego, o który pytano 2 lata wcześniej ....
Pacerier

Odpowiedzi:

191

Nie, nie ma na to sposobu. Zasadniczo byłoby to odwracanie zakresu.

Metody zdefiniowane w konstruktorze mają dostęp do zmiennych prywatnych, ponieważ wszystkie funkcje mają dostęp do zakresu, w którym zostały zdefiniowane.

Metody zdefiniowane w prototypie nie są zdefiniowane w zakresie konstruktora i nie będą miały dostępu do lokalnych zmiennych konstruktora.

Nadal możesz mieć prywatne zmienne, ale jeśli chcesz, aby metody zdefiniowane w prototypie miały do ​​nich dostęp, powinieneś zdefiniować thismetody pobierające i ustawiające na obiekcie, do którego będą miały dostęp metody prototypowe (wraz ze wszystkim innym) . Na przykład:

function Person(name, secret) {
    // public
    this.name = name;

    // private
    var secret = secret;

    // public methods have access to private members
    this.setSecret = function(s) {
        secret = s;
    }

    this.getSecret = function() {
        return secret;
    }
}

// Must use getters/setters 
Person.prototype.spillSecret = function() { alert(this.getSecret()); };
Tryptyk
źródło
14
„scoping in reverse” to funkcja C ++ ze słowem kluczowym „friend”. Zasadniczo każda funkcja powinna zdefiniować jej prototyp jako przyjaciela. Niestety ta koncepcja to C ++, a nie JS :(
TWiStErRob 21.09.13
1
Chciałbym dodać ten post na początek mojej listy ulubionych i zachować go tam.
Donato,
2
Nie widzę sensu - dodajesz tylko warstwę abstrakcji, która nic nie robi. Równie dobrze możesz zrobić secretwłasność this. JavaScript po prostu nie obsługuje zmiennych prywatnych z prototypami, ponieważ prototypy są powiązane z kontekstem strony wywołującej, a nie kontekstem strony tworzenia.
nicodemus13
1
Dlaczego więc nie zrobić person.getSecret()?
Fahmi
1
Dlaczego to ma tak wiele pozytywnych opinii? To nie czyni prywatnej zmiennej. Jak wspomniano powyżej, użycie person.getSecret () pozwoli ci uzyskać dostęp do tej prywatnej zmiennej z dowolnego miejsca.
alexr101
64

Aktualizacja: w ES6 istnieje lepszy sposób:

Krótko mówiąc, możesz użyć nowego Symboldo tworzenia prywatnych pól.
Oto świetny opis: https://curiosity-driven.org/private-properties-in-javascript

Przykład:

var Person = (function() {
    // Only Person can access nameSymbol
    var nameSymbol = Symbol('name');

    function Person(name) {
        this[nameSymbol] = name;
    }

    Person.prototype.getName = function() {
        return this[nameSymbol];
    };

    return Person;
}());

Dla wszystkich nowoczesnych przeglądarek z ES5:

Możesz użyć tylko Zamknięć

Najprostszym sposobem na konstruowanie obiektów jest całkowite uniknięcie dziedziczenia prototypowego. Wystarczy zdefiniować zmienne prywatne i funkcje publiczne w zamknięciu, a wszystkie metody publiczne będą miały prywatny dostęp do zmiennych.

Lub możesz użyć tylko prototypów

W JavaScript dziedziczenie prototypów jest przede wszystkim optymalizacją . Umożliwia wielu instancjom współużytkowanie metod prototypowych, a nie każda instancja ma własne metody.
Wadą jest to, że thisjest tylko rzeczą, która różni się za każdym razem prototypal funkcja jest wywoływana.
Dlatego wszelkie prywatne pola muszą być dostępne przez this, co oznacza, że ​​będą publiczne. Trzymamy się więc konwencji nazewnictwa dla _privatepól.

Nie zawracaj sobie głowy mieszaniem zamknięć z prototypami

Myślę, że nie powinieneś mieszać zmiennych zamknięcia z metodami prototypowymi. Powinieneś użyć jednego lub drugiego.

Gdy używasz zamknięcia w celu uzyskania dostępu do zmiennej prywatnej, metody prototypowe nie mogą uzyskać dostępu do zmiennej. Musisz więc ujawnić zamknięcie this, co oznacza, że ​​ujawniasz je publicznie w taki czy inny sposób. Dzięki takiemu podejściu niewiele można zyskać.

Który mam wybrać?

W przypadku naprawdę prostych obiektów wystarczy użyć zwykłego obiektu z zamknięciami.

Jeśli potrzebujesz dziedziczenia prototypowego - do dziedziczenia, wydajności itp. - trzymaj się konwencji nazewnictwa „_private” i nie zawracaj sobie głowy zamykaniem.

Nie rozumiem, dlaczego programiści JS tak bardzo starają się, aby pola były naprawdę prywatne.

Scott Rippey
źródło
4
Niestety, _privatekonwencja nazewnictwa jest nadal najlepszym rozwiązaniem, jeśli chcesz skorzystać z dziedziczenia prototypowego.
zmiażdżyć
1
ES6 będzie miał nową koncepcję, Symbolktóra jest doskonałym sposobem na tworzenie prywatnych pól. Oto świetne wyjaśnienie: ciekawość
driven.org/private-properties-in-javascript
1
Nie, możesz utrzymać Symbolzamknięcie obejmujące całą klasę. W ten sposób wszystkie prototypowe metody mogą korzystać z Symbolu, ale nigdy nie są ujawniane poza klasą.
Scott Rippey,
2
W artykule zostało połączone mówi „ Symbole są podobne do nazw prywatnych, ale - w przeciwieństwie do nazw prywatnych - oni nie dają prawdziwego prywatności . ”. Jeśli masz instancję, możesz uzyskać jej symbole za pomocą Object.getOwnPropertySymbols. Jest to więc prywatność tylko z powodu niejasności.
Oriol
2
@Oriol Tak, prywatność jest spowodowana dużym zaciemnieniem. Nadal można iterować po symbolach, a cel symbolu można wywnioskować za pośrednictwem toString. Nie różni się to od Java lub C # ... członkowie prywatni są nadal dostępni przez odbicie, ale zwykle są mocno zasłonięci. Wszystko to wzmacnia mój końcowy punkt: „Nie rozumiem, dlaczego programiści JS tak bardzo starają się, aby pola były naprawdę prywatne”.
Scott Rippey
31

Kiedy to przeczytałem, brzmiało to jak trudne wyzwanie, więc postanowiłem znaleźć sposób. Co wymyśliłem było CRAAAAZY ale całkowicie działa.

Najpierw próbowałem zdefiniować klasę w funkcji natychmiastowej, abyś miał dostęp do niektórych prywatnych właściwości tej funkcji. Działa to i pozwala uzyskać prywatne dane, jednak jeśli spróbujesz ustawić prywatne dane, wkrótce przekonasz się, że wszystkie obiekty będą miały tę samą wartość.

var SharedPrivateClass = (function() { // use immediate function
    // our private data
    var private = "Default";

    // create the constructor
    function SharedPrivateClass() {}

    // add to the prototype
    SharedPrivateClass.prototype.getPrivate = function() {
        // It has access to private vars from the immediate function!
        return private;
    };

    SharedPrivateClass.prototype.setPrivate = function(value) {
        private = value;
    };

    return SharedPrivateClass;
})();

var a = new SharedPrivateClass();
console.log("a:", a.getPrivate()); // "a: Default"

var b = new SharedPrivateClass();
console.log("b:", b.getPrivate()); // "b: Default"

a.setPrivate("foo"); // a Sets private to "foo"
console.log("a:", a.getPrivate()); // "a: foo"
console.log("b:", b.getPrivate()); // oh no, b.getPrivate() is "foo"!

console.log(a.hasOwnProperty("getPrivate")); // false. belongs to the prototype
console.log(a.private); // undefined

// getPrivate() is only created once and instanceof still works
console.log(a.getPrivate === b.getPrivate);
console.log(a instanceof SharedPrivateClass);
console.log(b instanceof SharedPrivateClass);

Jest wiele przypadków, w których byłoby to odpowiednie, na przykład gdybyś chciał mieć stałe wartości, takie jak nazwy zdarzeń, które są współużytkowane między instancjami. Ale zasadniczo działają one jak prywatne zmienne statyczne.

Jeśli absolutnie potrzebujesz dostępu do zmiennych w prywatnej przestrzeni nazw z metod zdefiniowanych w prototypie, możesz wypróbować ten wzorzec.

var PrivateNamespaceClass = (function() { // immediate function
    var instance = 0, // counts the number of instances
        defaultName = "Default Name",  
        p = []; // an array of private objects

    // create the constructor
    function PrivateNamespaceClass() {
        // Increment the instance count and save it to the instance. 
        // This will become your key to your private space.
        this.i = instance++; 
        
        // Create a new object in the private space.
        p[this.i] = {};
        // Define properties or methods in the private space.
        p[this.i].name = defaultName;
        
        console.log("New instance " + this.i);        
    }

    PrivateNamespaceClass.prototype.getPrivateName = function() {
        // It has access to the private space and it's children!
        return p[this.i].name;
    };
    PrivateNamespaceClass.prototype.setPrivateName = function(value) {
        // Because you use the instance number assigned to the object (this.i)
        // as a key, the values set will not change in other instances.
        p[this.i].name = value;
        return "Set " + p[this.i].name;
    };

    return PrivateNamespaceClass;
})();

var a = new PrivateNamespaceClass();
console.log(a.getPrivateName()); // Default Name

var b = new PrivateNamespaceClass();
console.log(b.getPrivateName()); // Default Name

console.log(a.setPrivateName("A"));
console.log(b.setPrivateName("B"));
console.log(a.getPrivateName()); // A
console.log(b.getPrivateName()); // B

// private objects are not accessible outside the PrivateNamespaceClass function
console.log(a.p);

// the prototype functions are not re-created for each instance
// and instanceof still works
console.log(a.getPrivateName === b.getPrivateName);
console.log(a instanceof PrivateNamespaceClass);
console.log(b instanceof PrivateNamespaceClass);

Chciałbym uzyskać informacje zwrotne od każdego, kto widzi błąd w tym sposobie.

Mims H. Wright
źródło
4
Wydaje mi się, że jedną z potencjalnych obaw jest to, że każda instancja może uzyskać dostęp do dowolnych innych prywatnych zmiennych przy użyciu innego identyfikatora instancji. Niekoniecznie zła rzecz ...
Mims H. Wright,
15
Przed
10
@ Lu4 Nie jestem pewien, czy to prawda. Konstruktor jest zwracany z zamknięcia; jedyne zdefiniowane funkcje prototypowe to pierwszy raz, w tym wywołanym natychmiastowo wyrażeniu funkcyjnym. Oprócz wspomnianych wyżej kwestii związanych z prywatnością, wygląda mi to dobrze (na pierwszy rzut oka).
guypursey
1
@ MimsH.Wright inne języki pozwalają na dostęp do innych obiektów prywatnych tej samej klasy , ale tylko wtedy, gdy masz do nich odniesienia. Aby to umożliwić, możesz ukryć szeregowców za funkcją, która przyjmuje wskaźnik obiektów jako klucz (w odniesieniu do identyfikatora). W ten sposób masz dostęp tylko do prywatnych danych obiektów, o których wiesz, co jest bardziej zgodne z zasięgiem w innych językach. Jednak ta implementacja rzuca światło na głębszy problem z tym. Prywatne obiekty nigdy nie będą odśmiecane, dopóki funkcja Konstruktora nie będzie.
Thomas Nadin
3
Chcę wspomnieć, że izostał dodany do wszystkich instancji. Nie jest więc w pełni „przezroczysty” i imoże być nadal modyfikowany.
Scott Rippey
18

patrz strona Douga Crockforda na ten temat . Musisz to zrobić pośrednio za pomocą czegoś, co może uzyskać dostęp do zakresu zmiennej prywatnej.

inny przykład:

Incrementer = function(init) {
  var counter = init || 0;  // "counter" is a private variable
  this._increment = function() { return counter++; }
  this._set = function(x) { counter = x; }
}
Incrementer.prototype.increment = function() { return this._increment(); }
Incrementer.prototype.set = function(x) { return this._set(x); }

przypadek użycia:

js>i = new Incrementer(100);
[object Object]
js>i.increment()
100
js>i.increment()
101
js>i.increment()
102
js>i.increment()
103
js>i.set(-44)
js>i.increment()
-44
js>i.increment()
-43
js>i.increment()
-42
Jason S.
źródło
47
Ten przykład wydaje się okropną praktyką. Zastosowanie prototypowych metod polega na tym, że nie musisz tworzyć nowej dla każdej instancji. I tak to robisz. Dla każdej metody tworzysz inną.
Kir
2
@ArmedMonkey Koncepcja wygląda na dobrą, ale zgodziła się, że jest to zły przykład, ponieważ przedstawione funkcje prototypowe są trywialne. Gdyby funkcje prototypowe były funkcjami znacznie dłuższymi, wymagającymi prostego dostępu do zestawu „prywatnych” zmiennych, miałoby to sens.
naleśnik
9
Dlaczego nawet przeszkadzało wystawiając _setpoprzez set? Dlaczego nie nazwać tego setna początek?
Scott Rippey
15

Sugeruję, że prawdopodobnie dobrym pomysłem byłoby opisanie „posiadania przypisania prototypu w konstruktorze” jako anty-wzorca Javascript. Pomyśl o tym. To jest zbyt ryzykowne.

To, co tak naprawdę robisz przy tworzeniu drugiego obiektu (tj. B), zmienia definicję tej funkcji prototypu dla wszystkich obiektów, które używają tego prototypu. Spowoduje to skuteczne zresetowanie wartości obiektu a w twoim przykładzie. Będzie działać, jeśli chcesz udostępnić zmienną i jeśli zdarzy się, że utworzysz wszystkie instancje obiektów z góry, ale wydaje się to zbyt ryzykowne.

Znalazłem błąd w Javascript, nad którym ostatnio pracowałem, spowodowany dokładnie tym anty-wzorem. Próbował ustawić funkcję przeciągania i upuszczania dla konkretnego tworzonego obiektu, ale zamiast tego robił to dla wszystkich instancji. Niedobrze.

Rozwiązanie Douga Crockforda jest najlepsze.

Lance Ewing
źródło
10

@Kai

To nie zadziała. Jeśli zrobisz

var t2 = new TestClass();

t2.prototypeHellobędzie wtedy dostęp do prywatnej sekcji t.

@AnglesCrimes

Przykładowy kod działa dobrze, ale w rzeczywistości tworzy „statyczny” prywatny element wspólny dla wszystkich instancji. Morgankody tego rozwiązania mogą nie być rozwiązaniem.

Do tej pory nie znalazłem łatwego i czystego sposobu na zrobienie tego bez wprowadzenia prywatnego skrótu i ​​dodatkowych funkcji czyszczenia. Funkcję członka prywatnego można symulować do pewnego stopnia:

(function() {
    function Foo() { ... }
    Foo.prototype.bar = function() {
       privateFoo.call(this, blah);
    };
    function privateFoo(blah) { 
        // scoped to the instance by passing this to call 
    }

    window.Foo = Foo;
}());
Tim
źródło
Zrozumiałeś jasno swoje punkty, ale czy możesz wyjaśnić, co chcesz zrobić we fragmencie kodu?
Vishwanath,
privateFoojest całkowicie prywatny, a zatem niewidoczny podczas uzyskiwania new Foo(). bar()Jest tu tylko metoda publiczna, do której ma dostęp privateFoo. Możesz użyć tego samego mechanizmu do prostych zmiennych i obiektów, jednak zawsze należy pamiętać, że privatessą one w rzeczywistości statyczne i będą wspólne dla wszystkich tworzonych obiektów.
Philzen
6

Tak, to możliwe. Wzorzec projektowy PPF właśnie to rozwiązuje.

PPF oznacza prywatne funkcje prototypowe. Podstawowe PPF rozwiązuje następujące problemy:

  1. Funkcje prototypowe uzyskują dostęp do danych prywatnych.
  2. Funkcje prototypowe można ustawić jako prywatne.

Po pierwsze:

  1. Umieść wszystkie prywatne zmienne instancji, które mają być dostępne z funkcji prototypowych, w osobnym kontenerze danych i
  2. Przekaż odwołanie do kontenera danych do wszystkich funkcji prototypowych jako parametr.

To takie proste. Na przykład:

// Helper class to store private data.
function Data() {};

// Object constructor
function Point(x, y)
{
  // container for private vars: all private vars go here
  // we want x, y be changeable via methods only
  var data = new Data;
  data.x = x;
  data.y = y;

  ...
}

// Prototype functions now have access to private instance data
Point.prototype.getX = function(data)
{
  return data.x;
}

Point.prototype.getY = function(data)
{
  return data.y;
}

...

Przeczytaj całą historię tutaj:

Wzorzec projektowy PPF

Edward
źródło
4
Odpowiedzi tylko na łącze są ogólnie mile widziane na SO. Pokaż przykład.
Corey Adler,
Artykuł zawiera przykłady, więc proszę tam zajrzeć
Edward,
5
Co się jednak stanie, jeśli w pewnym momencie strona przestanie działać? Jak więc ktoś powinien zobaczyć przykład? Polityka jest na miejscu, aby można było zachować wszystko, co ma wartość w linku, i nie musieć polegać na stronie internetowej, która nie jest pod naszą kontrolą.
Corey Adler,
3
@ Edward, twój link to ciekawa lektura! Wydaje mi się jednak, że głównym powodem dostępu do prywatnych danych za pomocą funkcji prototypowych jest zapobieganie marnowaniu pamięci przez każdy obiekt o identycznych funkcjach publicznych. Opisana metoda nie rozwiązuje tego problemu, ponieważ do użytku publicznego funkcja prototypowa musi być zapakowana w zwykłą funkcję publiczną. Myślę, że wzorzec może być przydatny do oszczędzania pamięci, jeśli masz wiele ppf, które są połączone w jedną funkcję publiczną. Czy używasz ich do czegoś innego?
Dining Philosopher
@DiningPhilosofer, dziękuję za docenienie mojego artykułu. Tak, masz rację, nadal korzystamy z funkcji instancji. Ale pomysł polega na tym, aby były tak lekkie, jak to tylko możliwe, po prostu ponownie dzwoniąc do swoich odpowiedników PPF, które wykonują całą ciężką pracę. W końcu wszystkie instancje wywołują te same pliki PPF (oczywiście przez opakowania), więc można oczekiwać pewnej oszczędności pamięci. Pytanie tylko, ile. Spodziewam się znacznych oszczędności.
Edward
5

Możesz to faktycznie osiągnąć, korzystając z weryfikacji Accessor :

(function(key, global) {
  // Creates a private data accessor function.
  function _(pData) {
    return function(aKey) {
      return aKey === key && pData;
    };
  }

  // Private data accessor verifier.  Verifies by making sure that the string
  // version of the function looks normal and that the toString function hasn't
  // been modified.  NOTE:  Verification can be duped if the rogue code replaces
  // Function.prototype.toString before this closure executes.
  function $(me) {
    if(me._ + '' == _asString && me._.toString === _toString) {
      return me._(key);
    }
  }
  var _asString = _({}) + '', _toString = _.toString;

  // Creates a Person class.
  var PersonPrototype = (global.Person = function(firstName, lastName) {
    this._ = _({
      firstName : firstName,
      lastName : lastName
    });
  }).prototype;
  PersonPrototype.getName = function() {
    var pData = $(this);
    return pData.firstName + ' ' + pData.lastName;
  };
  PersonPrototype.setFirstName = function(firstName) {
    var pData = $(this);
    pData.firstName = firstName;
    return this;
  };
  PersonPrototype.setLastName = function(lastName) {
    var pData = $(this);
    pData.lastName = lastName;
    return this;
  };
})({}, this);

var chris = new Person('Chris', 'West');
alert(chris.setFirstName('Christopher').setLastName('Webber').getName());

Ten przykład pochodzi z mojego postu o funkcjach prototypowych i prywatnych danych i jest tam wyjaśniony bardziej szczegółowo.

Chris West
źródło
1
Ta odpowiedź jest zbyt „sprytna”, aby była użyteczna, ale podoba mi się użycie zmiennej powiązanej z IFFE jako tajnego uścisku dłoni. Ta implementacja używa zbyt wielu zamknięć, aby była przydatna; celem prototypowych metod jest zapobieganie budowie nowych obiektów funkcji dla każdej metody na każdym obiekcie.
greg.kindel
Podejście to wykorzystuje tajny klucz, aby określić, które metody prototypowe są zaufane, a które nie. Jednak to instancja sprawdza poprawność klucza, więc klucz należy wysłać do instancji. Ale wtedy niezaufany kod mógłby wywołać zaufaną metodę na fałszywej instancji, co ukradłoby klucz. Za pomocą tego klucza twórz nowe metody, które będą uznawane za zaufane przez rzeczywiste instancje. Jest to więc prywatność tylko z powodu niejasności.
Oriol
4

W obecnym JavaScript jestem dość pewien, że istnieje jeden i jedyny sposób na uzyskanie stanu prywatnego , dostępnego z funkcji prototypowych , bez dodawania do niego niczego publicznegothis . Odpowiedzią jest użycie wzorca „słabej mapy”.

Podsumowując: PersonKlasa ma jedną słabą mapę, w której kluczami są instancje Person, a wartości to zwykłe obiekty, które są używane do prywatnego przechowywania.

Oto w pełni funkcjonalny przykład: (zagraj na http://jsfiddle.net/ScottRippey/BLNVr/ )

var Person = (function() {
    var _ = weakMap();
    // Now, _(this) returns an object, used for private storage.
    var Person = function(first, last) {
        // Assign private storage:
        _(this).firstName = first;
        _(this).lastName = last;
    }
    Person.prototype = {
        fullName: function() {
            // Retrieve private storage:
            return _(this).firstName + _(this).lastName;
        },
        firstName: function() {
            return _(this).firstName;
        },
        destroy: function() {
            // Free up the private storage:
            _(this, true);
        }
    };
    return Person;
})();

function weakMap() {
    var instances=[], values=[];
    return function(instance, destroy) {
        var index = instances.indexOf(instance);
        if (destroy) {
            // Delete the private state:
            instances.splice(index, 1);
            return values.splice(index, 1)[0];
        } else if (index === -1) {
            // Create the private state:
            instances.push(instance);
            values.push({});
            return values[values.length - 1];
        } else {
            // Return the private state:
            return values[index];
        }
    };
}

Tak jak powiedziałem, to naprawdę jedyny sposób na osiągnięcie wszystkich 3 części.

Istnieją jednak dwa zastrzeżenia. Po pierwsze, kosztuje to wydajność - za każdym razem, gdy uzyskujesz dostęp do prywatnych danych, jest to O(n)operacja, w której njest liczba instancji. Nie będziesz chciał tego robić, jeśli masz dużą liczbę instancji. Po drugie, kiedy skończysz z instancją, musisz zadzwonić destroy; w przeciwnym razie instancja i dane nie zostaną usunięte, a skończy się wyciekiem pamięci.

I dlatego chciałbym trzymać się mojej oryginalnej odpowiedzi „Nie powinieneś” .

Scott Rippey
źródło
Jeśli nie zniszczysz jawnie instancji Osoby, zanim wykroczy ona poza zakres, czy mapa słaba nie zachowuje odniesienia do niej, abyś miał wyciek pamięci? Wymyśliłem wzór chroniony, ponieważ inne instancje Person mogą uzyskać dostęp do zmiennej i osoby dziedziczące od Person mogą. Po prostu go przerobiłem, więc nie jestem pewien, czy istnieją jakieś wady inne niż dodatkowe przetwarzanie (nie wygląda to tak, jak dostęp do prywatnych) stackoverflow.com/a/21800194/1641941 Zwracanie prywatnego / chronionego obiektu jest uciążliwe od czasu wywołania kodu może następnie zmutować twój prywatny / chroniony.
HMR
2
@HMR Tak, musisz jawnie zniszczyć prywatne dane. Dodam to zastrzeżenie do mojej odpowiedzi.
Scott Rippey
3

Istnieje prostszy sposób, wykorzystując wykorzystanie metod bindi call.

Ustawiając prywatne zmienne na obiekt, możesz wykorzystać zasięg tego obiektu.

Przykład

function TestClass (value) {
    // The private value(s)
    var _private = {
        value: value
    };

    // `bind` creates a copy of `getValue` when the object is instantiated
    this.getValue = TestClass.prototype.getValue.bind(_private);

    // Use `call` in another function if the prototype method will possibly change
    this.getValueDynamic = function() {
        return TestClass.prototype.getValue.call(_private);
    };
};

TestClass.prototype.getValue = function() {
    return this.value;
};

Ta metoda nie jest pozbawiona wad. Ponieważ kontekst zasięgu jest skutecznie zastępowany, nie masz dostępu poza _privateobiektem. Jednak nie jest niemożliwe zapewnienie dostępu do zakresu obiektu instancji. Możesz przekazać w kontekście obiektu ( this) jako drugi argument do bindlub callnadal mieć dostęp do jego wartości publicznych w funkcji prototypu.

Dostęp do wartości publicznych

function TestClass (value) {
    var _private = {
        value: value
    };

    this.message = "Hello, ";

    this.getMessage = TestClass.prototype.getMessage.bind(_private, this);

}

TestClass.prototype.getMessage = function(_public) {

    // Can still access passed in arguments
    // e.g. – test.getValues('foo'), 'foo' is the 2nd argument to the method
    console.log([].slice.call(arguments, 1));
    return _public.message + this.value;
};

var test = new TestClass("World");
test.getMessage(1, 2, 3); // [1, 2, 3]         (console.log)
                          // => "Hello, World" (return value)

test.message = "Greetings, ";
test.getMessage(); // []                    (console.log)
                   // => "Greetings, World" (return value)
thgaskell
źródło
2
Dlaczego ktoś miałby tworzyć kopię metody prototypowej, a nie tylko tworzyć instancję metody?
zmiażdżyć
3

Spróbuj!

    function Potatoe(size) {
    var _image = new Image();
    _image.src = 'potatoe_'+size+'.png';
    function getImage() {
        if (getImage.caller == null || getImage.caller.owner != Potatoe.prototype)
            throw new Error('This is a private property.');
        return _image;
    }
    Object.defineProperty(this,'image',{
        configurable: false,
        enumerable: false,
        get : getImage          
    });
    Object.defineProperty(this,'size',{
        writable: false,
        configurable: false,
        enumerable: true,
        value : size            
    });
}
Potatoe.prototype.draw = function(ctx,x,y) {
    //ctx.drawImage(this.image,x,y);
    console.log(this.image);
}
Potatoe.prototype.draw.owner = Potatoe.prototype;

var pot = new Potatoe(32);
console.log('Potatoe size: '+pot.size);
try {
    console.log('Potatoe image: '+pot.image);
} catch(e) {
    console.log('Oops: '+e);
}
pot.draw();
AlanNLohse
źródło
1
Zależy callerto od rozszerzenia zależnego od implementacji, które nie jest dozwolone w trybie ścisłym.
Oriol
1

Oto, co wymyśliłem.

(function () {
    var staticVar = 0;
    var yrObj = function () {
        var private = {"a":1,"b":2};
        var MyObj = function () {
            private.a += staticVar;
            staticVar++;
        };
        MyObj.prototype = {
            "test" : function () {
                console.log(private.a);
            }
        };

        return new MyObj;
    };
    window.YrObj = yrObj;
}());

var obj1 = new YrObj;
var obj2 = new YrObj;
obj1.test(); // 1
obj2.test(); // 2

głównym problemem związanym z tą implementacją jest to, że redefiniuje prototypy przy każdej instanacji.

Xeltor
źródło
Interesujące, naprawdę podoba mi się ta próba i myślałem o tym samym, ale masz rację, że przedefiniowanie funkcji prototypu przy każdej instancji jest dość dużym ograniczeniem. Nie dzieje się tak tylko dlatego, że zmarnowane cykle procesora, ale dlatego, że jeśli kiedykolwiek zmienisz prototoype, zostanie on „zresetowany” z powrotem do pierwotnego stanu zdefiniowanego w konstruktorze przy następnej instancji: /
Niko Bellic
1
To nie tylko przedefiniowało prototypy, ale także definiuje nowego konstruktora dla każdej instancji. Zatem „instancje” nie są już instancjami tej samej klasy.
Oriol
1

Jest na to bardzo prosty sposób

function SharedPrivate(){
  var private = "secret";
  this.constructor.prototype.getP = function(){return private}
  this.constructor.prototype.setP = function(v){ private = v;}
}

var o1 = new SharedPrivate();
var o2 = new SharedPrivate();

console.log(o1.getP()); // secret
console.log(o2.getP()); // secret
o1.setP("Pentax Full Frame K1 is on sale..!");
console.log(o1.getP()); // Pentax Full Frame K1 is on sale..!
console.log(o2.getP()); // Pentax Full Frame K1 is on sale..!
o2.setP("And it's only for $1,795._");
console.log(o1.getP()); // And it's only for $1,795._

Prototypy JavaScript są złote.

Redu
źródło
2
Uważam, że lepiej nie używać prototypu w funkcji konstruktora, ponieważ utworzy on nową funkcję za każdym razem, gdy tworzona jest nowa instancja.
whamsicore
@whamsicore Tak prawda, ale w tym przypadku jest to niezbędne, ponieważ dla każdego obiektu, który został utworzony, musimy zorganizować wspólne zamknięcie. To jest powód, dla którego definicje funkcji znajdują się wewnątrz konstruktora i musimy się do tego odwoływać, SharedPrivate.prototypeponieważ this.constructor.prototypewiele razy przedefiniowanie getP i setP jest wiele razy ...
Redu
1

Spóźniam się na przyjęcie, ale myślę, że mogę się przyłączyć. Sprawdź to:

// 1. Create closure
var SomeClass = function() {
  // 2. Create `key` inside a closure
  var key = {};
  // Function to create private storage
  var private = function() {
    var obj = {};
    // return Function to access private storage using `key`
    return function(testkey) {
      if(key === testkey) return obj;
      // If `key` is wrong, then storage cannot be accessed
      console.error('Cannot access private properties');
      return undefined;
    };
  };
  var SomeClass = function() {
    // 3. Create private storage
    this._ = private();
    // 4. Access private storage using the `key`
    this._(key).priv_prop = 200;
  };
  SomeClass.prototype.test = function() {
    console.log(this._(key).priv_prop); // Using property from prototype
  };
  return SomeClass;
}();

// Can access private property from within prototype
var instance = new SomeClass();
instance.test(); // `200` logged

// Cannot access private property from outside of the closure
var wrong_key = {};
instance._(wrong_key); // undefined; error logged

Nazywam tę metodę wzorcem akcesorium . Podstawową ideą jest to, że mamy zamknięcie , klucz wewnątrz zamknięcia i tworzymy prywatny obiekt (w konstruktorze), do którego można uzyskać dostęp tylko, jeśli masz klucz .

Jeśli jesteś zainteresowany, możesz przeczytać więcej na ten temat w moim artykule . Za pomocą tej metody można tworzyć właściwości dla każdego obiektu, do których nie można uzyskać dostępu poza zamknięciem. Dlatego możesz ich używać w konstruktorze lub prototypie, ale nigdzie indziej. Nigdzie nie widziałem tej metody, ale myślę, że jest naprawdę potężna.

gitara
źródło
0

Nie możesz umieścić zmiennych w wyższym zakresie?

(function () {
    var privateVariable = true;

    var MyClass = function () {
        if (privateVariable) console.log('readable from private scope!');
    };

    MyClass.prototype.publicMethod = function () {
        if (privateVariable) console.log('readable from public scope!');
    };
}))();
Ev Haus
źródło
4
Następnie zmienne są dzielone między wszystkie instancje MyClass.
zmiażdżyć
0

Możesz także spróbować dodać metodę nie bezpośrednio na prototypie, ale na funkcji konstruktora w następujący sposób:

var MyArray = function() {
    var array = [];

    this.add = MyArray.add.bind(null, array);
    this.getAll = MyArray.getAll.bind(null, array);
}

MyArray.add = function(array, item) {
    array.push(item);
}
MyArray.getAll = function(array) {
    return array;
}

var myArray1 = new MyArray();
myArray1.add("some item 1");
console.log(myArray1.getAll()); // ['some item 1']
var myArray2 = new MyArray();
myArray2.add("some item 2");
console.log(myArray2.getAll()); // ['some item 2']
console.log(myArray1.getAll()); // ['some item 2'] - FINE!
Maciej Dzikowicki
źródło
0

Oto coś, co wymyśliłem, próbując znaleźć najprostsze rozwiązanie tego problemu, być może może być przydatne dla kogoś. Jestem nowy w javascript, więc mogą być pewne problemy z kodem.

// pseudo-class definition scope
(function () {

    // this is used to identify 'friend' functions defined within this scope,
    // while not being able to forge valid parameter for GetContext() 
    // to gain 'private' access from outside
    var _scope = new (function () { })();
    // -----------------------------------------------------------------

    // pseudo-class definition
    this.Something = function (x) {

        // 'private' members are wrapped into context object,
        // it can be also created with a function
        var _ctx = Object.seal({

            // actual private members
            Name: null,
            Number: null,

            Somefunc: function () {
                console.log('Something(' + this.Name + ').Somefunc(): number = ' + this.Number);
            }
        });
        // -----------------------------------------------------------------

        // function below needs to be defined in every class
        // to allow limited access from prototype
        this.GetContext = function (scope) {

            if (scope !== _scope) throw 'access';
            return _ctx;
        }
        // -----------------------------------------------------------------

        {
            // initialization code, if any
            _ctx.Name = (x !== 'undefined') ? x : 'default';
            _ctx.Number = 0;

            Object.freeze(this);
        }
    }
    // -----------------------------------------------------------------

    // prototype is defined only once
    this.Something.prototype = Object.freeze({

        // public accessors for 'private' field
        get Number() { return this.GetContext(_scope).Number; },
        set Number(v) { this.GetContext(_scope).Number = v; },

        // public function making use of some private fields
        Test: function () {

            var _ctx = this.GetContext(_scope);
            // access 'private' field
            console.log('Something(' + _ctx.Name + ').Test(): ' + _ctx.Number);
            // call 'private' func
            _ctx.Somefunc();
        }
    });
    // -----------------------------------------------------------------

    // wrap is used to hide _scope value and group definitions
}).call(this);

function _A(cond) { if (cond !== true) throw new Error('assert failed'); }
// -----------------------------------------------------------------

function test_smth() {

    console.clear();

    var smth1 = new Something('first'),
      smth2 = new Something('second');

    //_A(false);
    _A(smth1.Test === smth2.Test);

    smth1.Number = 3;
    smth2.Number = 5;
    console.log('smth1.Number: ' + smth1.Number + ', smth2.Number: ' + smth2.Number);

    smth1.Number = 2;
    smth2.Number = 6;

    smth1.Test();
    smth2.Test();

    try {
        var ctx = smth1.GetContext();
    } catch (err) {
        console.log('error: ' + err);
    }
}

test_smth();
V.Mihaly4
źródło
0

Dzisiaj stanąłem przed dokładnie tym samym pytaniem i po opracowaniu pierwszej klasy odpowiedzi Scotta Rippeya wymyśliłem bardzo proste rozwiązanie (IMHO), które jest zarówno kompatybilne z ES5, jak i wydajne, a także bezpieczne w przypadku kolizji nazw (używanie _private wydaje się niebezpieczne) .

/*jslint white: true, plusplus: true */

 /*global console */

var a, TestClass = (function(){
    "use strict";
    function PrefixedCounter (prefix) {
        var counter = 0;
        this.count = function () {
            return prefix + (++counter);
        };
    }
    var TestClass = (function(){
        var cls, pc = new PrefixedCounter("_TestClass_priv_")
        , privateField = pc.count()
        ;
        cls = function(){
            this[privateField] = "hello";
            this.nonProtoHello = function(){
                console.log(this[privateField]);
            };
        };
        cls.prototype.prototypeHello = function(){
            console.log(this[privateField]);
        };
        return cls;
    }());
    return TestClass;
}());

a = new TestClass();
a.nonProtoHello();
a.prototypeHello();

Testowane z ringojs i nodejs. Z chęcią przeczytam twoją opinię.

alexgirao
źródło
Oto odniesienie: sprawdź sekcję „Krok bliżej”. philipwalton.com/articles/…
jimasun
0
var getParams = function(_func) {
  res = _func.toString().split('function (')[1].split(')')[0].split(',')
  return res
}

function TestClass(){

  var private = {hidden: 'secret'}
  //clever magic accessor thing goes here
  if ( !(this instanceof arguments.callee) ) {
    for (var key in arguments) {
      if (typeof arguments[key] == 'function') {
        var keys = getParams(arguments[key])
        var params = []
        for (var i = 0; i <= keys.length; i++) {
          if (private[keys[i]] != undefined) {
            params.push(private[keys[i]])
          }
        }
        arguments[key].apply(null,params)
      }
    }
  }
}


TestClass.prototype.test = function(){
  var _hidden; //variable I want to get
  TestClass(function(hidden) {_hidden = hidden}) //invoke magic to get
};

new TestClass().test()

Jak to jest Korzystanie z prywatnego akcesora. Pozwala tylko uzyskać zmienne, ale nie ustawiać ich, zależy od przypadku użycia.

dylan0150
źródło
To nie odpowiada na pytanie w użyteczny sposób. dlaczego uważasz, że to jest odpowiedź? jak to działa Samo powiedzenie komuś, by zmienił kod bez żadnego kontekstu lub znaczenia, nie pomaga mu dowiedzieć się, co zrobili źle.
GrumpyCrouton
Chciał sposobu na dostęp do ukrytych zmiennych prywatnych klasy za pomocą prototypów bez konieczności tworzenia tej ukrytej zmiennej na każdej instancji klasy. Powyższy kod jest przykładową metodą takiego działania. Jak to nie jest odpowiedź na pytanie?
dylan0150
Nie powiedziałem, że to nie jest odpowiedź na pytanie. Powiedziałem, że nie jest to przydatna odpowiedź, ponieważ nie pomaga nikomu się uczyć. Powinieneś wyjaśnić swój kod, dlaczego to działa, dlaczego jest to właściwy sposób, aby to zrobić. Gdybym był autorem pytania, nie zaakceptowałbym twojej odpowiedzi, ponieważ nie zachęca do nauki, nie uczy mnie, co robię źle, co robi dany kod ani jak działa.
GrumpyCrouton
0

Mam jedno rozwiązanie, ale nie jestem pewien, czy jest bez wad.

Aby to zadziałało, musisz użyć następującej struktury:

  1. Użyj 1 obiektu prywatnego, który zawiera wszystkie zmienne prywatne.
  2. Użyj 1 funkcji wystąpienia.
  3. Zastosuj zamknięcie do konstruktora i wszystkich funkcji prototypowych.
  4. Każde utworzone wystąpienie odbywa się poza zdefiniowanym zamknięciem.

Oto kod:

var TestClass = 
(function () {
    // difficult to be guessed.
    var hash = Math.round(Math.random() * Math.pow(10, 13) + + new Date());
    var TestClass = function () {
        var privateFields = {
            field1: 1,
            field2: 2
        };
        this.getPrivateFields = function (hashed) {
            if(hashed !== hash) {
                throw "Cannot access private fields outside of object.";
                // or return null;
            }
            return privateFields;
        };
    };

    TestClass.prototype.prototypeHello = function () {
        var privateFields = this.getPrivateFields(hash);
        privateFields.field1 = Math.round(Math.random() * 100);
        privateFields.field2 = Math.round(Math.random() * 100);
    };

    TestClass.prototype.logField1 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field1);
    };

    TestClass.prototype.logField2 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field2);
    };

    return TestClass;
})();

Działa to tak, że udostępnia funkcję instancji „this.getPrivateFields” w celu uzyskania dostępu do obiektu zmiennych prywatnych „privateFields”, ale funkcja ta zwróci tylko obiekt „privateFields” w zdefiniowanym głównym zamknięciu (również funkcje prototypowe za pomocą „this.getPrivateFields” „należy zdefiniować wewnątrz tego zamknięcia).

Hash wytworzony podczas działania i trudny do odgadnięcia służy jako parametry, aby upewnić się, że nawet jeśli wywołanie „getPrivateFields” wykracza poza zakres zamknięcia, nie zwróci obiektu „privateFields”.

Wadą jest to, że nie możemy rozszerzyć TestClass o więcej funkcji prototypowych poza zamknięciem.

Oto kod testowy:

var t1 = new TestClass();
console.log('Initial t1 field1 is: ');
t1.logField1();
console.log('Initial t1 field2 is: ');
t1.logField2();
t1.prototypeHello();
console.log('t1 field1 is now: ');
t1.logField1();
console.log('t1 field2 is now: ');
t1.logField2();
var t2 = new TestClass();
console.log('Initial t2 field1 is: ');
t2.logField1();
console.log('Initial t2 field2 is: ');
t2.logField2();
t2.prototypeHello();
console.log('t2 field1 is now: ');
t2.logField1();
console.log('t2 field2 is now: ');
t2.logField2();

console.log('t1 field1 stays: ');
t1.logField1();
console.log('t1 field2 stays: ');
t1.logField2();

t1.getPrivateFields(11233);

EDYCJA: Za pomocą tej metody można również „zdefiniować” funkcje prywatne.

TestClass.prototype.privateFunction = function (hashed) {
    if(hashed !== hash) {
        throw "Cannot access private function.";
    }
};

TestClass.prototype.prototypeHello = function () {
    this.privateFunction(hash);
};
Jannes Botis
źródło
0

Bawiłem się tym dzisiaj i było to jedyne rozwiązanie, jakie mogłem znaleźć bez użycia symboli. Najlepszą rzeczą jest to, że w rzeczywistości wszystko może być całkowicie prywatne.

Rozwiązanie opiera się na autorskim module ładującym, który w zasadzie staje się pośrednikiem prywatnej pamięci podręcznej (przy użyciu słabej mapy).

   const loader = (function() {
        function ModuleLoader() {}

    //Static, accessible only if truly needed through obj.constructor.modules
    //Can also be made completely private by removing the ModuleLoader prefix.
    ModuleLoader.modulesLoaded = 0;
    ModuleLoader.modules = {}

    ModuleLoader.prototype.define = function(moduleName, dModule) {
        if (moduleName in ModuleLoader.modules) throw new Error('Error, duplicate module');

        const module = ModuleLoader.modules[moduleName] = {}

        module.context = {
            __moduleName: moduleName,
            exports: {}
        }

        //Weak map with instance as the key, when the created instance is garbage collected or goes out of scope this will be cleaned up.
        module._private = {
            private_sections: new WeakMap(),
            instances: []
        };

        function private(action, instance) {
            switch (action) {
                case "create":
                    if (module._private.private_sections.has(instance)) throw new Error('Cannot create private store twice on the same instance! check calls to create.')
                    module._private.instances.push(instance);
                    module._private.private_sections.set(instance, {});
                    break;
                case "delete":
                    const index = module._private.instances.indexOf(instance);
                    if (index == -1) throw new Error('Invalid state');
                    module._private.instances.slice(index, 1);
                    return module._private.private_sections.delete(instance);
                    break;
                case "get":
                    return module._private.private_sections.get(instance);
                    break;
                default:
                    throw new Error('Invalid action');
                    break;
            }
        }

        dModule.call(module.context, private);
        ModuleLoader.modulesLoaded++;
    }

    ModuleLoader.prototype.remove = function(moduleName) {
        if (!moduleName in (ModuleLoader.modules)) return;

        /*
            Clean up as best we can.
        */
        const module = ModuleLoader.modules[moduleName];
        module.context.__moduleName = null;
        module.context.exports = null;
        module.cotext = null;
        module._private.instances.forEach(function(instance) { module._private.private_sections.delete(instance) });
        for (let i = 0; i < module._private.instances.length; i++) {
            module._private.instances[i] = undefined;
        }
        module._private.instances = undefined;
        module._private = null;
        delete ModuleLoader.modules[moduleName];
        ModuleLoader.modulesLoaded -= 1;
    }


    ModuleLoader.prototype.require = function(moduleName) {
        if (!(moduleName in ModuleLoader.modules)) throw new Error('Module does not exist');

        return ModuleLoader.modules[moduleName].context.exports;
    }



     return new ModuleLoader();
    })();

    loader.define('MyModule', function(private_store) {
        function MyClass() {
            //Creates the private storage facility. Called once in constructor.
            private_store("create", this);


            //Retrieve the private storage object from the storage facility.
            private_store("get", this).no = 1;
        }

        MyClass.prototype.incrementPrivateVar = function() {
            private_store("get", this).no += 1;
        }

        MyClass.prototype.getPrivateVar = function() {
            return private_store("get", this).no;
        }

        this.exports = MyClass;
    })

    //Get whatever is exported from MyModule
    const MyClass = loader.require('MyModule');

    //Create a new instance of `MyClass`
    const myClass = new MyClass();

    //Create another instance of `MyClass`
    const myClass2 = new MyClass();

    //print out current private vars
    console.log('pVar = ' + myClass.getPrivateVar())
    console.log('pVar2 = ' + myClass2.getPrivateVar())

    //Increment it
    myClass.incrementPrivateVar()

    //Print out to see if one affected the other or shared
    console.log('pVar after increment = ' + myClass.getPrivateVar())
    console.log('pVar after increment on other class = ' + myClass2.getPrivateVar())

    //Clean up.
    loader.remove('MyModule')
Eladian
źródło
0

Wiem, że minęła ponad dekada, odkąd o to poproszono, ale po prostu zastanowiłem się nad tym po raz n-ty w życiu programisty i znalazłem możliwe rozwiązanie, którego nie wiem, czy całkowicie mi się podoba . Nie widziałem wcześniej tej metodologii, więc nadam jej nazwę „prywatny / publiczny wzór dolara” lub _ $ / $ $ .

var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]);
var ownFieldValue = this._$("fieldName"[, newValue]);

var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]);

//Throws an exception. objectX._$ is not defined
var objectFieldValue = objectX._$("fieldName"[, newValue]);

Koncepcja wykorzystuje funkcję ClassDefinition, która zwraca funkcję konstruktora, która zwraca obiekt interfejsu . Jedyną metodą interfejsu jest $otrzymanie nameargumentu w celu wywołania odpowiedniej funkcji w obiekcie konstruktora, a wszelkie dodatkowe argumenty przekazane później namesą przekazywane w wywołaniu.

Globalnie zdefiniowanej funkcji pomocnika ClassValuesprzechowuje wszystkie pola z obiektu w razie potrzeby. Określa _$funkcję dostępu do nich name. Jest to zgodne z krótkim wzorcem get / set, więc jeśli valuezostanie przekazany, zostanie użyty jako nowa wartość zmiennej.

var ClassValues = function (values) {
  return {
    _$: function _$(name, value) {
      if (arguments.length > 1) {
        values[name] = value;
      }

      return values[name];
    }
  };
};

Globalnie zdefiniowana funkcja Interfacepobiera obiekt i Valuesobiekt _interfacez jedną funkcją, $która sprawdza, czy objznaleźć funkcję nazwaną na podstawie parametru namei wywołuje ją valuesjako obiekt o zasięgu . Dodatkowe przekazane argumenty $zostaną przekazane przy wywołaniu funkcji.

var Interface = function (obj, values, className) {
  var _interface = {
    $: function $(name) {
      if (typeof(obj[name]) === "function") {
        return obj[name].apply(values, Array.prototype.splice.call(arguments, 1));
      }

      throw className + "." + name + " is not a function.";
    }
  };

  //Give values access to the interface.
  values.$ = _interface.$;

  return _interface;
};

W poniższym przykładzie ClassXjest przypisany do wyniku ClassDefinition, który jest Constructorfunkcją. Constructormoże otrzymać dowolną liczbę argumentów. Interfaceto, co otrzymuje kod zewnętrzny po wywołaniu konstruktora.

var ClassX = (function ClassDefinition () {
  var Constructor = function Constructor (valA) {
    return Interface(this, ClassValues({ valA: valA }), "ClassX");
  };

  Constructor.prototype.getValA = function getValA() {
    //private value access pattern to get current value.
    return this._$("valA");
  };

  Constructor.prototype.setValA = function setValA(valA) {
    //private value access pattern to set new value.
    this._$("valA", valA);
  };

  Constructor.prototype.isValAValid = function isValAValid(validMessage, invalidMessage) {
    //interface access pattern to call object function.
    var valA = this.$("getValA");

    //timesAccessed was not defined in constructor but can be added later...
    var timesAccessed = this._$("timesAccessed");

    if (timesAccessed) {
      timesAccessed = timesAccessed + 1;
    } else {
      timesAccessed = 1;
    }

    this._$("timesAccessed", timesAccessed);

    if (valA) {
      return "valA is " + validMessage + ".";
    }

    return "valA is " + invalidMessage + ".";
  };

  return Constructor;
}());

Nie ma sensu mieć funkcji nie prototypowych Constructor, chociaż można je zdefiniować w treści funkcji konstruktora. Wszystkie funkcje są wywoływane z publicznym wzorem dolara this.$("functionName"[, param1[, param2 ...]]) . Dostęp do prywatnych wartości uzyskuje się za pomocą prywatnego wzoru dolara this._$("valueName"[, replacingValue]); . Ponieważ Interfacenie ma definicji _$, do obiektów zewnętrznych nie można uzyskać dostępu do wartości. Ponieważ każde prototypowane ciało funkcji thisjest ustawione na valuesobiekt w funkcji $, otrzymasz wyjątki, jeśli bezpośrednio wywołasz funkcje rodzeństwa Konstruktora; te _ $ / $ wzór musi być następnie w organach prototypy funkcji też. Poniżej przykładowe użycie.

var classX1 = new ClassX();
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
console.log("classX1.valA: " + classX1.$("getValA"));
classX1.$("setValA", "v1");
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
var classX2 = new ClassX("v2");
console.log("classX1.valA: " + classX1.$("getValA"));
console.log("classX2.valA: " + classX2.$("getValA"));
//This will throw an exception
//classX1._$("valA");

I wyjście konsoli.

classX1.valA is invalid.
classX1.valA: undefined
classX1.valA is valid.
classX1.valA: v1
classX2.valA: v2

_ $ / $ Wzór pozwala na pełną prywatność wartościami w pełni prototyp klas. Nie wiem, czy kiedykolwiek tego użyję, czy też ma wady, ale hej, to była dobra łamigłówka!

JPortillo
źródło
0

ES6 WeakMaps

Za pomocą prostego wzorca opartego na ES6 WeakMaps można uzyskać prywatne zmienne składowe , dostępne z funkcji prototypowych .

Uwaga: użycie WeakMaps gwarantuje bezpieczeństwo przed wyciekiem pamięci , pozwalając Garbage Collectorowi identyfikować i odrzucać nieużywane instancje.

// Create a private scope using an Immediately 
// Invoked Function Expression...
let Person = (function() {

    // Create the WeakMap that will hold each  
    // Instance collection's of private data
    let privateData = new WeakMap();
    
    // Declare the Constructor :
    function Person(name) {
        // Insert the private data in the WeakMap,
        // using 'this' as a unique acces Key
        privateData.set(this, { name: name });
    }
    
    // Declare a prototype method 
    Person.prototype.getName = function() {
        // Because 'privateData' is in the same 
        // scope, it's contents can be retrieved...
        // by using  again 'this' , as  the acces key 
        return privateData.get(this).name;
    };

    // return the Constructor
    return Person;
}());

Bardziej szczegółowe wyjaśnienie tego wzoru można znaleźć tutaj

colxi
źródło
-1

Musisz zmienić 3 rzeczy w kodzie:

  1. Wymień var privateField = "hello"się this.privateField = "hello".
  2. W prototypie zastąpić privateFieldz this.privateField.
  3. W braku prototypu również wymienić privateFieldz this.privateField.

Ostateczny kod byłby następujący:

TestClass = function(){
    this.privateField = "hello";
    this.nonProtoHello = function(){alert(this.privateField)};
}

TestClass.prototype.prototypeHello = function(){alert(this.privateField)};

var t = new TestClass();

t.prototypeHello()
A-Sharabiani
źródło
this.privateFieldnie byłoby polem prywatnym. jest dostępny z zewnątrz:t.privateField
V. Rubinetti
-2

W definicji konstruktora można użyć przypisania prototypu.

Zmienna będzie widoczna dla dodanej metody prototypu, ale wszystkie instancje funkcji będą miały dostęp do tej samej zmiennej SHARED.

function A()
{
  var sharedVar = 0;
  this.local = "";

  A.prototype.increment = function(lval)
  {    
    if (lval) this.local = lval;    
    alert((++sharedVar) + " while this.p is still " + this.local);
  }
}

var a = new A();
var b = new A();    
a.increment("I belong to a");
b.increment("I belong to b");
a.increment();
b.increment();

Mam nadzieję, że to może się przydać.

AngelsCrimes
źródło