Prywatne metody JavaScript


482

Aby utworzyć klasę JavaScript metodą publiczną, zrobiłbym coś takiego:

function Restaurant() {}

Restaurant.prototype.buy_food = function(){
   // something here
}

Restaurant.prototype.use_restroom = function(){
   // something here
}

W ten sposób użytkownicy mojej klasy mogą:

var restaurant = new Restaurant();
restaurant.buy_food();
restaurant.use_restroom();

Jak utworzyć prywatną metodę, która może być wywoływana przez metody buy_foodi use_restroom, ale nie zewnętrznie przez użytkowników klasy?

Innymi słowy, chcę, aby moja metoda implementacji była w stanie:

Restaurant.prototype.use_restroom = function() {
   this.private_stuff();
}

Ale to nie powinno działać:

var r = new Restaurant();
r.private_stuff();

Jak zdefiniować private_stuffjako metodę prywatną, aby oba były prawdziwe?

Przeczytałem kilka słów Douga Crockforda, ale nie wydaje się, że metody „prywatne” można wywoływać metodami publicznymi, a metody „uprzywilejowane” można wywoływać zewnętrznie.

Odpowiedzi:


403

Możesz to zrobić, ale wadą jest to, że nie może być częścią prototypu:

function Restaurant() {
    var myPrivateVar;

    var private_stuff = function() {  // Only visible inside Restaurant()
        myPrivateVar = "I can set this here!";
    }

    this.use_restroom = function() {  // use_restroom is visible to all
        private_stuff();
    }

    this.buy_food = function() {   // buy_food is visible to all
        private_stuff();
    }
}

9
Ukrywanie cienkich stron w zamknięciu nie gwarantuje prywatności wszystkim tłumaczom. Zobacz code.google.com/p/google-caja/wiki/…
Mike Samuel

51
@mikesamuel - prawda, ale tylko wtedy, gdy ci tłumacze mają w sobie błędy :)
jvenema

133
Jest to prywatna metoda, ale zużywa dużo więcej pamięci niż zwykła metoda prototypowa, zwłaszcza jeśli tworzysz wiele takich obiektów. Dla każdej instancji obiektu tworzy osobną funkcję powiązaną z obiektem, a nie z klasą. Ponadto nie są gromadzone śmieci, dopóki sam obiekt nie zostanie zniszczony.
Arindam

4
Jeśli utworzysz obiekt, który McDonalds () odziedziczy po Restaurant (), McDonalds nie może zastąpić prywatnych metod, jeśli je zadeklarujesz w ten sposób. Cóż, właściwie możesz, ale to nie zadziała, jeśli jakaś inna metoda wywoła metodę prywatną, wywoła oryginalną wersję metody i nie będziesz mógł wywołać metody nadrzędnej. Do tej pory nie znalazłem dobrego sposobu na zadeklarowanie prywatnych metod, które działają dobrze z dziedziczeniem. To i implikacje dotyczące wydajności sprawiają, że wcale nie jest to bardzo dobry wzorzec projektowy. Poleciłbym wykonanie jakiegoś prefiksu w celu wskazania metod prywatnych, na przykład rozpoczynając od podkreślenia.
Hoffmann

68
Metody prywatne nie powinny być nadpisywane - są prywatne.
17 z 26

163

Korzystanie z funkcji wywoływania i wywoływania

JavaScript używa prototypów i nie ma klas (ani metod w tym zakresie), takich jak języki obiektowe. Programista JavaScript musi myśleć w JavaScript.

Cytat z Wikipedii:

W przeciwieństwie do wielu języków obiektowych nie ma rozróżnienia między definicją funkcji a definicją metody. Rozróżnienie występuje raczej podczas wywoływania funkcji; gdy funkcja jest wywoływana jako metoda obiektu, lokalna funkcja tego słowa kluczowego jest powiązana z tym obiektem dla tego wywołania.

Rozwiązanie wykorzystujące funkcję auto-wywoływania i funkcję call do wywołania prywatnej „metody”:

var MyObject = (function () {

    // Constructor
    function MyObject (foo) {
        this._foo = foo;
    }

    function privateFun (prefix) {
        return prefix + this._foo;
    }

    MyObject.prototype.publicFun = function () {
        return privateFun.call(this, '>>');
    }

    return MyObject;
})();


var myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // ReferenceError: private is not defined

Funkcja call pozwala nam wywoływać funkcję prywatną z odpowiednim kontekstem ( this).


Łatwiej dzięki Node.js

Jeśli używasz node.js , nie potrzebujesz IIFE, ponieważ możesz skorzystać z systemu ładowania modułów :

function MyObject (foo) {
    this._foo = foo;
}

function privateFun (prefix) {
    return prefix + this._foo;
}

MyObject.prototype.publicFun = function () {
    return privateFun.call(this, '>>');
}

exports.MyObject = MyObject;

Załaduj plik:

var MyObject = require('./MyObject').MyObject;

var myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // ReferenceError: private is not defined


(eksperymentalnie) ES7 z operatorem wiązania

Operator powiązania ::jest propozycją ECMAScript i jest implementowany w Babel ( etap 0 ).

export default class MyObject {
  constructor (foo) {
    this._foo = foo;
  }

  publicFun () {
    return this::privateFun('>>');
  }
}

function privateFun (prefix) {
  return prefix + this._foo;
}

Załaduj plik:

import MyObject from './MyObject';

let myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // TypeError: myObject.privateFun is not a function

34
To najlepsza odpowiedź. Korzyści z prywatnych metod, bez śmieci.
TaylorMac,

1
@TaylorMac Z wyjątkiem .callczęści.
pishpish

1
@janje Huh? To jest sedno pytania, nie ma prywatnego f()w javascript (nie w tej chwili).
Yves M.

2
@YvesM. Chodzi o to, aby wybrać najlepszy wzór, który go symuluje.
pishpish

1
@ zmieniono, jaki jest najlepszy kompromis, aby mieć ukryte funkcje (niewymagalne z zewnątrz), ładny sintax (nie .call) i małą pamięć (brak funkcji w instancji)? Czy to w ogóle istnieje?
Ciprian Tomoiagă

161

Możesz symulować takie metody prywatne:

function Restaurant() {
}

Restaurant.prototype = (function() {
    var private_stuff = function() {
        // Private code here
    };

    return {

        constructor:Restaurant,

        use_restroom:function() {
            private_stuff();
        }

    };
})();

var r = new Restaurant();

// This will work:
r.use_restroom();

// This will cause an error:
r.private_stuff();

Więcej informacji na temat tej techniki tutaj: http://webreflection.blogspot.com/2008/04/natural-javascript-private-methods.html


7
Sugerowałbym również witrynę Douglasa Crockforda jako źródło informacji na temat prywatnych / publicznych metod i członków javascript.crockford.com/private.html
Jared,

10
Wspomniał o tym linku w pytaniu.
Gulzar Nazim

8
Minusem tej metody jest to, że nie możesz mieć dostępu do prywatnych danych w restauracji przez private_stuff (), a inne metody restauracji nie mogą wywoływać private_stuff (). Plusem jest to, że jeśli nie potrzebujesz żadnego z wyżej wymienionych warunków, możesz zachować use_restroom () w prototypie.
17 z 26

6
To powinno być rozwiązanie i zaakceptowana odpowiedź, ponieważ autor wyraźnie używa właściwości prototypu.
Gabriel Llamas

23
Zgodnie ze wzorem zaproponowanym przez @georgebrock wszystkie prywatne dane będą udostępniane wszystkim obiektom restauracji. Jest to podobne do statycznych zmiennych prywatnych i funkcji w OOP opartym na klasach. Zakładam, że PO tego nie chce. Aby zilustrować, co mam na myśli, stworzyłem jsFiddle .
feklee

35

W tych sytuacjach, gdy masz publiczny interfejs API i potrzebujesz prywatnych / publicznych metod / właściwości, zawsze używam Wzorca modułu. Ten wzór stał się popularny w bibliotece YUI, a szczegóły można znaleźć tutaj:

http://yuiblog.com/blog/2007/06/12/module-pattern/

Jest to naprawdę proste i łatwe do zrozumienia dla innych programistów. Dla prostego przykładu:

var MYLIB = function() {  
    var aPrivateProperty = true;
    var aPrivateMethod = function() {
        // some code here...
    };
    return {
        aPublicMethod : function() {
            aPrivateMethod(); // okay
            // some code here...
        },
        aPublicProperty : true
    };  
}();

MYLIB.aPrivateMethod() // not okay
MYLIB.aPublicMethod() // okay

tego rodzaju rzeczy nie zostałyby wykryte przez autouzupełnianie IDE :(
Kliknij Upvote

19
Ale to nie jest klasa, więc nie możesz mieć 2 „wystąpień” tego w różnych stanach.
DevAntoine,

usuń część () i masz „klasę”. przynajmniej tam, gdzie można tworzyć różne instancje z różnymi stanami. wzorzec modułu jest jednak dość intensywny dla pamięci ...
oligofren

@DevAntoine Spójrz na komentarze do odpowiedzi 17 z 26. W JavaScript klasy rozszerzalne i metody prywatne nie idą łatwo w parze. Moją sugestią w tym przypadku byłoby pójście z kompozycją nad dziedziczeniem. Utwórz rozszerzalny prototyp przy użyciu tych samych metod, co zamknięty obiekt betonowy. Następnie wewnątrz swojego prototypu możesz zdecydować, kiedy wywołać metody na konkretnym obiekcie.

Czy jest jakiś minusem nazywając zmienne publiczne od prywatnych funkcji tak: aPrivateMethod = function() { MYLIB.aPublicProperty}?
Hanna

21

Oto klasa, którą stworzyłem, aby zrozumieć, co Douglas Crockford zaproponował w swojej witrynie Private Members w JavaScript

function Employee(id, name) { //Constructor
    //Public member variables
    this.id = id;
    this.name = name;
    //Private member variables
    var fName;
    var lName;
    var that = this;
    //By convention, we create a private variable 'that'. This is used to     
    //make the object available to the private methods. 

    //Private function
    function setFName(pfname) {
        fName = pfname;
        alert('setFName called');
    }
    //Privileged function
    this.setLName = function (plName, pfname) {
        lName = plName;  //Has access to private variables
        setFName(pfname); //Has access to private function
        alert('setLName called ' + this.id); //Has access to member variables
    }
    //Another privileged member has access to both member variables and private variables
    //Note access of this.dataOfBirth created by public member setDateOfBirth
    this.toString = function () {
        return 'toString called ' + this.id + ' ' + this.name + ' ' + fName + ' ' + lName + ' ' + this.dataOfBirth; 
    }
}
//Public function has access to member variable and can create on too but does not have access to private variable
Employee.prototype.setDateOfBirth = function (dob) {
    alert('setDateOfBirth called ' + this.id);
    this.dataOfBirth = dob;   //Creates new public member note this is accessed by toString
    //alert(fName); //Does not have access to private member
}
$(document).ready()
{
    var employee = new Employee(5, 'Shyam'); //Create a new object and initialize it with constructor
    employee.setLName('Bhaskar', 'Ram');  //Call privileged function
    employee.setDateOfBirth('1/1/2000');  //Call public function
    employee.id = 9;                     //Set up member value
    //employee.setFName('Ram');  //can not call Private Privileged method
    alert(employee.toString());  //See the changed object

}

5
That = to wzór, który jest dość powszechny, spopularyzowany przez wspomnianego wcześniej Crockforda w jego książce „Javascript: The good parts”
oligofren,

8
thatjest używany zamiast, thisaby uniknąć problemów z zasięgiem, gdy funkcje są powiązane z innym obiektem. Tutaj są przechowywane thisw thati nigdy ponownym użyciem, która jest taka sama, jak nie robi tego w ogóle. Powinieneś zmienić za thispomocą thatwszystkich Constructorfunkcji wewnętrznych (nie deklaracji metod). Jeśli employeejest applyedytowane lub calledytowane w jakiś sposób, metody te mogą rzucać, ponieważ thiszostaną niepoprawnie powiązane.
Maroshii

Ponadto każda instancja będzie miała pełną kopię funkcji prywatnych, nieefektywną. Poza tym, że metody publiczne nie mają dostępu do prywatnych klas vars, sprawia, że ​​chcę przejść na rzutki. Niestety angulardart jest super beta.
Ray Foss

Gdzie jest w tym metoda „konstruktora”? Gdzie położyłbym logikę, która normalnie byłaby wykonywana w metodzie konstruktora klasy, gdy jest ona tworzona?
BadHorsie,

13

Przywołałem to: EDYCJA: Właściwie ktoś powiązał to samo rozwiązanie. Hه!

var Car = function() {
}

Car.prototype = (function() {
    var hotWire = function() {
        // Private code *with* access to public properties through 'this'
        alert( this.drive() ); // Alerts 'Vroom!'
    }

    return {
        steal: function() {
            hotWire.call( this ); // Call a private method
        },
        drive: function() {
            return 'Vroom!';
        }
    };
})();

var getAwayVechile = new Car();

hotWire(); // Not allowed
getAwayVechile.hotWire(); // Not allowed
getAwayVechile.steal(); // Alerts 'Vroom!'

1
To fajna technika, ale jak pozwoliłbyś parametrom w swoim konstruktorze? Na przykład var getAwayVehicle = new Car(100);gdzie 100jest prędkość i chcesz ostrzec prędkość. Dzięki!
Jason

1
Zrozumiałem, mogę mieć var Car = function(speed) { this.speed = speed; }i `return {constructor: Car, ...`
Jason

11

Myślę, że takie pytania wciąż pojawiają się z powodu braku zrozumienia zamknięć. Zamknięcia są najważniejsze w JS. Każdy programista JS musi poczuć jego esencję.

1. Przede wszystkim musimy wprowadzić osobny zakres (zamknięcie).

function () {

}

2. W tym obszarze możemy robić, co chcemy. I nikt się o tym nie dowie.

function () {
    var name,
        secretSkills = {
            pizza: function () { return new Pizza() },
            sushi: function () { return new Sushi() }
        }

    function Restaurant(_name) {
        name = _name
    }
    Restaurant.prototype.getFood = function (name) {
        return name in secretSkills ? secretSkills[name]() : null
    }
}

3. Aby świat wiedział o naszej klasie restauracji, musimy ją zwrócić po zamknięciu.

var Restaurant = (function () {
    // Restaurant definition
    return Restaurant
})()

4. Na koniec mamy:

var Restaurant = (function () {
    var name,
        secretSkills = {
            pizza: function () { return new Pizza() },
            sushi: function () { return new Sushi() }
        }

    function Restaurant(_name) {
        name = _name
    }
    Restaurant.prototype.getFood = function (name) {
        return name in secretSkills ? secretSkills[name]() : null
    }
    return Restaurant
})()

5. Podejście to ma również potencjał dziedziczenia i tworzenia szablonów

// Abstract class
function AbstractRestaurant(skills) {
    var name
    function Restaurant(_name) {
        name = _name
    }
    Restaurant.prototype.getFood = function (name) {
        return skills && name in skills ? skills[name]() : null
    }
    return Restaurant
}

// Concrete classes
SushiRestaurant = AbstractRestaurant({ 
    sushi: function() { return new Sushi() } 
})

PizzaRestaurant = AbstractRestaurant({ 
    pizza: function() { return new Pizza() } 
})

var r1 = new SushiRestaurant('Yo! Sushi'),
    r2 = new PizzaRestaurant('Dominos Pizza')

r1.getFood('sushi')
r2.getFood('pizza')

Mam nadzieję, że to pomoże komuś lepiej zrozumieć ten temat


2
To, co masz w punkcie 4., jest cudowne! Myślę, że to jedyna odpowiedź ze wszystkich tutaj, w których zyskujesz zarówno wzrost wydajności / pamięci wynikający z używania metod na prototypie, jak i te publiczne metody mają pełny dostęp do członków prywatnych. +1
Hudon

7
To nie działa Zmienna nazwy tutaj działa jak zmienna statyczna i jest wspólna dla wszystkich instancji Restauracji. Oto jsbin: jsbin.com/oqewUWa/2/edit?js,output
Shital Shah

to dobra próba, ale jak zauważył Shital, zmienna nazwy jest błędna.
oligofren

2
dodanie mojego 2c tutaj, aby potwierdzić, że to nie działa, wygląda ładnie, ale jak wspomniano powyżej, „nazwa” będzie służyć jako zmienna statyczna, tj. wspólna dla wszystkich instancji
Paul Carroll

10

Osobiście wolę następujący wzór do tworzenia klas w JavaScript:

var myClass = (function() {
    // Private class properties go here

    var blueprint = function() {
        // Private instance properties go here
        ...
    };

    blueprint.prototype = { 
        // Public class properties go here
        ...
    };

    return  {
         // Public class properties go here
        create : function() { return new blueprint(); }
        ...
    };
})();

Jak widać, pozwala zdefiniować zarówno właściwości klasy, jak i właściwości instancji, z których każda może być publiczna i prywatna.


Próbny

var Restaurant = function() {
    var totalfoodcount = 0;        // Private class property
    var totalrestroomcount  = 0;   // Private class property
    
    var Restaurant = function(name){
        var foodcount = 0;         // Private instance property
        var restroomcount  = 0;    // Private instance property
        
        this.name = name
        
        this.incrementFoodCount = function() {
            foodcount++;
            totalfoodcount++;
            this.printStatus();
        };
        this.incrementRestroomCount = function() {
            restroomcount++;
            totalrestroomcount++;
            this.printStatus();
        };
        this.getRestroomCount = function() {
            return restroomcount;
        },
        this.getFoodCount = function() {
            return foodcount;
        }
    };
   
    Restaurant.prototype = {
        name : '',
        buy_food : function(){
           this.incrementFoodCount();
        },
        use_restroom : function(){
           this.incrementRestroomCount();
        },
        getTotalRestroomCount : function() {
            return totalrestroomcount;
        },
        getTotalFoodCount : function() {
            return totalfoodcount;
        },
        printStatus : function() {
           document.body.innerHTML
               += '<h3>Buying food at '+this.name+'</h3>'
               + '<ul>' 
               + '<li>Restroom count at ' + this.name + ' : '+ this.getRestroomCount() + '</li>'
               + '<li>Food count at ' + this.name + ' : ' + this.getFoodCount() + '</li>'
               + '<li>Total restroom count : '+ this.getTotalRestroomCount() + '</li>'
               + '<li>Total food count : '+ this.getTotalFoodCount() + '</li>'
               + '</ul>';
        }
    };

    return  { // Singleton public properties
        create : function(name) {
            return new Restaurant(name);
        },
        printStatus : function() {
          document.body.innerHTML
              += '<hr />'
              + '<h3>Overview</h3>'
              + '<ul>' 
              + '<li>Total restroom count : '+ Restaurant.prototype.getTotalRestroomCount() + '</li>'
              + '<li>Total food count : '+ Restaurant.prototype.getTotalFoodCount() + '</li>'
              + '</ul>'
              + '<hr />';
        }
    };
}();

var Wendys = Restaurant.create("Wendy's");
var McDonalds = Restaurant.create("McDonald's");
var KFC = Restaurant.create("KFC");
var BurgerKing = Restaurant.create("Burger King");

Restaurant.printStatus();

Wendys.buy_food();
Wendys.use_restroom();
KFC.use_restroom();
KFC.use_restroom();
Wendys.use_restroom();
McDonalds.buy_food();
BurgerKing.buy_food();

Restaurant.printStatus();

BurgerKing.buy_food();
Wendys.use_restroom();
McDonalds.buy_food();
KFC.buy_food();
Wendys.buy_food();
BurgerKing.buy_food();
McDonalds.buy_food();

Restaurant.printStatus();

Zobacz także to skrzypce .


To sprawia, że chcę używać ES6 klas i zobaczyć co to transpiles zbyt
sheriffderek

9

Całe to zamknięcie będzie cię kosztować. Upewnij się, że przetestujesz wpływ na szybkość, szczególnie w IE. Przekonasz się, że lepiej się czujesz dzięki konwencji nazewnictwa. Nadal istnieje wielu użytkowników korporacyjnych, którzy są zmuszeni do korzystania z IE6 ...


34
Kogo to obchodzi, poważnie?
nowayyy

17
9% wciąż używających IE6 nie dba o szybkość, optymalizacje i wszystkie nowoczesne funkcje HTML5. Więc zamknięcia nic nie kosztują.
Gabriel Llamas

6
Teraz jest 0,5% (sierpień 2012 r.) W3schools.com/browsers/browsers_explorer.asp
Lorenzo Polidori

7
@LorenzoPolidori użytkownicy w3schools! == użytkownicy korporacyjni;]
WynandB

Konwencja nazewnictwa (np. Przygotowywanie podkreślenia) jest właściwą drogą. Kod jest łatwiejszy w utrzymaniu, a metody są nadal zdefiniowane w prototypie. Jednak w dzisiejszych czasach ... Po prostu zaznaczam metodę jako prywatną na maszynie.
David Sherret,

5

Nie bądź taki gadatliwy. To Javascript. Użyj konwencji nazewnictwa .

Po latach pracy w klasach es6, niedawno rozpocząłem pracę nad projektem es5 (używając wymagającego JS, który już jest bardzo gadatliwy). Cały czas omawiałem wszystkie wymienione tutaj strategie i wszystko sprowadza się do użycia konwencji nazewnictwa :

  1. JavaScript nie ma słów kluczowych w zakresie jak private . Inni programiści, którzy wprowadzają Javascript, będą z góry o tym wiedzieć. Dlatego prosta konwencja nazewnictwa jest więcej niż wystarczająca. Prosta konwencja nazewnictwa z prefiksem ze znakiem podkreślenia rozwiązuje problem zarówno właściwości prywatnych, jak i metod prywatnych.
  2. Skorzystajmy z Prototypu ze względu na szybkość, ale nie pozwalajmy na więcej gadatliwości. Starajmy się, aby „klasa” es5 wyglądała tak dokładnie, jak możemy oczekiwać w innych językach zaplecza (i traktuj każdy plik jak klasę, nawet jeśli nie musimy zwracać instancji).
  3. Zademonstrujmy z bardziej realistyczną sytuacją modułu (użyjemy starego es5 i starego wymaganegoJ).

my-tooltip.js

    define([
        'tooltip'
    ],
    function(
        tooltip
    ){

        function MyTooltip() {
            // Later, if needed, we can remove the underscore on some
            // of these (make public) and allow clients of our class
            // to set them.
            this._selector = "#my-tooltip"
            this._template = 'Hello from inside my tooltip!';
            this._initTooltip();
        }

        MyTooltip.prototype = {
            constructor: MyTooltip,

            _initTooltip: function () {
                new tooltip.tooltip(this._selector, {
                    content: this._template,
                    closeOnClick: true,
                    closeButton: true
                });
            }
        }

        return {
            init: function init() {
               new MyTooltip();  // <-- Our constructor adds our tooltip to the DOM so not much we need to do after instantiation.
            }

            // You could instead return a new instantiation, 
            // if later you do more with this class.
            /* 
            create: function create() {
               return new MyTooltip();
            }
            */
        }
    });

2
Należy zauważyć, że ani język Javascript, ani żaden typowy host przeglądarki nie definiuje żadnych obiektów, które opierają się na konwencjach nazewnictwa w celu „ukrywania” stanu prywatnego, więc chociaż masz rację, że programiści zrozumieją tę koncepcję, nadal prowadzi to do bardzo Podejście OO do programowania OO.
rich remer

Czy mogę prosić o dobre referencje przy tworzeniu takich? W tym przykładzie są części, które są dla mnie nowe. defineI constructora sama struktura. Chociaż zgadzam się głównie z odpowiedzią, zacząłem pracować z JS z dużym wpływem OOP, a nawet przeskoczyłem zbyt wcześnie na TS, ponieważ miałem wcześniejsze doświadczenie w C #. Myślę, że muszę się tego oduczyć i zrozumieć paradygmat prototypowania / procedury. (upvoted, btw)
Cold Cerberus

1
@ColdCerberus ten fragment kodu używa es5. Pełny obraz tego podejścia można zobaczyć tutaj: gist.github.com/jonnyreeves/2474026 . Należy jednak pamiętać, będziemy chcieli skorzystać z tej metody i zaktualizować go za pomocą klas ES6: googlechrome.github.io/samples/classes-es6 i ES6 moduły (import / eksport składni) hackernoon.com/...
prograhammer

5

Możesz to zrobić teraz za pomocą prywatnych metod es10 . Musisz tylko dodać #przed nazwą metody.

class ClassWithPrivateMethod {
  #privateMethod() {
    return 'hello world';
  }

  getPrivateMessage() {
    return #privateMethod();
  }
}

2
Tyle że jest to etap 3 i nie jest jeszcze oficjalnie częścią języka.
misterhtmlcss

3

Wybierz jedno z rozwiązań zgodnych z prywatnym lub uprzywilejowanym wzorem Crockford . Na przykład:

function Foo(x) {
    var y = 5;
    var bar = function() {
        return y * x;
    };

    this.public = function(z) {
        return bar() + x * z;
    };
}

W każdym przypadku, gdy atakujący nie ma prawa „wykonania” w kontekście JS, nie ma możliwości uzyskania dostępu do jakichkolwiek pól lub metod „publicznych” lub „prywatnych”. W przypadku, gdy atakujący ma taki dostęp, może wykonać jedną linijkę:

eval("Foo = " + Foo.toString().replace(
    /{/, "{ this.eval = function(code) { return eval(code); }; "
));

Zauważ, że powyższy kod jest ogólny dla prywatności wszystkich konstruktorów. Nie powiedzie się z niektórymi rozwiązaniami tutaj, ale powinno być jasne, że prawie wszystkie rozwiązania oparte na zamknięciu mogą zostać zepsute w ten sposób z różnymireplace() parametrach.

Po wykonaniu tego każdy obiekt utworzony za pomocą new Foo()będzie miał evalmetodę, która może zostać wywołana w celu zwrócenia lub zmiany wartości lub metod zdefiniowanych w zamknięciu konstruktora, np .:

f = new Foo(99);
f.eval("x");
f.eval("y");
f.eval("x = 8");

Jedyny problem, jaki mogę z tym zobaczyć, nie działa w przypadkach, w których istnieje tylko jedna instancja i jest tworzona podczas ładowania. Ale wtedy nie ma powodu, aby faktycznie definiować prototyp. W takim przypadku atakujący może po prostu odtworzyć obiekt zamiast konstruktora, o ile ma on możliwość przekazania tych samych parametrów (np. Są one stałe lub obliczane na podstawie dostępnych wartości).

Moim zdaniem dzięki temu rozwiązanie Crockforda jest bezużyteczne.Ponieważ „prywatność” jest łatwo łamana, wady jego rozwiązania (zmniejszona czytelność i łatwość konserwacji, zmniejszona wydajność, zwiększona pamięć) sprawiają, że metoda oparta na prototypach „brak prywatności” jest lepszym wyborem.

Ja zwykle korzystam wiodącym podkreślenia aby oznaczyć __privatei _protectedmetod i pól (styl Perl), ale idea posiadania prywatności w JavaScripcie tylko pokazuje, w jaki sposób jest to język niezrozumiany.

Dlatego nie zgadzam się z Crockfordem wyjątkiem jego pierwszego zdania.

Jak więc uzyskać prawdziwą prywatność w JS? Umieść wszystko, co jest wymagane, aby być prywatnym po stronie serwera i używaj JS do wykonywania połączeń AJAX.


To poważny problem, który powinien być bardziej znany. Czy istnieje „obrona” przed tym atakiem?
James

@James Brak, o którym wiem, myślę, że taka jest natura bestii. Jak wskazałem, możesz przenieść funkcjonalność na serwer, na którym działa on w chronionym środowisku. Chodzi mi jednak o to, że rozwiązanie Crockforda nie pomaga, niepotrzebnie komplikuje kod i ukrywa konieczność zaradzenia temu.
Fozi

Jeśli użytkownik wprowadzi tajne hasło, nie może tego zrobić po stronie serwera. W pewnym momencie hasło będzie miało „prywatny” wariant. Czy osoba atakująca może to przeczytać? Ufam mojemu kodowi, a zresztą moje standardy domowe nie pozwalają na eval (). Atakującym może być złośliwa wtyczka lub biblioteka JavaScript innej firmy, której nie sprawdziłem poprawnie - więc tak, muszę to sprawdzić. Atakujący może być również czymś w rodzaju reklamy z boku, która nie powinna wchodzić w interakcje z moim kodem. Chronię się przed tym, pakując cały mój kod w anonimowy (function () {allMyStuff}());sposób, aby nie ujawniać niczego globalnego.
James

@James To robi się OT, jeśli chcesz kontynuować, otwórz nowe pytanie. Tak, atakujący może odczytać hasło. Z Twojej zmiennej „prywatnej”. Lub z DOM. Lub może zastąpić interfejs API AJAX. Lub zastępuje twoją stronę czymś innym. Jeśli nie może wykonać żadnej z powyższych czynności, nie ma potrzeby „prywatności” JS, ponieważ nie może on również odczytać żadnych zmiennych JS. Chodzi o to, że „rozwiązanie” Crockforda, z którego wszyscy korzystają obecnie, nie pomaga w rozwiązaniu tego problemu.
Fozi

Uważam, że zaciemnianie kodu pseudolosowego może być słabą obroną przed tym atakiem - trudniej jest zmodyfikować ciało funkcji, gdy nie można polegać na funkcji o ustalonej nazwie; trudniejsze, f.eval('nameOfVariable')gdy nie wiesz, co to 'nameOfVariable'jest ...
Gershom


2

Jeśli chcesz mieć pełny zakres funkcji publicznych i prywatnych z możliwością dostępu funkcji publicznych do funkcji publicznych, kod układu dla takiego obiektu:

function MyObject(arg1, arg2, ...) {
  //constructor code using constructor arguments...
  //create/access public variables as 
  // this.var1 = foo;

  //private variables

  var v1;
  var v2;

  //private functions
  function privateOne() {
  }

  function privateTwon() {
  }

  //public functions

  MyObject.prototype.publicOne = function () {
  };

  MyObject.prototype.publicTwo = function () {
  };
}

Czy ktoś może mi powiedzieć, dlaczego to zostało odrzucone? Dla mnie wygląda dobrze.
thomasrutter

10
Za każdym razem, gdy wykonasz a new MyObject, prototyp MyObjectjest zastępowany tymi samymi wartościami.
bpierre,

2
-1. Nigdy nie przypisuj do .prototypekonstruktora.
Bergi,

2
var TestClass = function( ) {

    var privateProperty = 42;

    function privateMethod( ) {
        alert( "privateMethod, " + privateProperty );
    }

    this.public = {
        constructor: TestClass,

        publicProperty: 88,
        publicMethod: function( ) {
            alert( "publicMethod" );
            privateMethod( );
        }
    };
};
TestClass.prototype = new TestClass( ).public;


var myTestClass = new TestClass( );

alert( myTestClass.publicProperty );
myTestClass.publicMethod( );

alert( myTestClass.privateMethod || "no privateMethod" );

Podobne do georgebrock, ale trochę mniej gadatliwe (IMHO) Masz problemy z zrobieniem tego w ten sposób? (Nigdzie go nie widziałem)

edycja: Zdałem sobie sprawę, że jest to trochę bezużyteczne, ponieważ każda niezależna instancja ma swoją własną kopię metod publicznych, co podważa użycie prototypu.


2

Oto, co do tej pory najbardziej podobało mi się w zakresie prywatnych / publicznych metod / członków i tworzenia instancji w javascript:

oto artykuł: http://www.sefol.com/?p=1090

a oto przykład:

var Person = (function () {

    //Immediately returns an anonymous function which builds our modules 
    return function (name, location) {

        alert("createPerson called with " + name);

        var localPrivateVar = name;

        var localPublicVar = "A public variable";

        var localPublicFunction = function () {
            alert("PUBLIC Func called, private var is :" + localPrivateVar)
        };

        var localPrivateFunction = function () {
            alert("PRIVATE Func called ")
        };

        var setName = function (name) {

            localPrivateVar = name;

        }

        return {

            publicVar: localPublicVar,

            location: location,

            publicFunction: localPublicFunction,

            setName: setName

        }

    }
})();


//Request a Person instance - should print "createPerson called with ben"
var x = Person("ben", "germany");

//Request a Person instance - should print "createPerson called with candide"
var y = Person("candide", "belgium");

//Prints "ben"
x.publicFunction();

//Prints "candide"
y.publicFunction();

//Now call a public function which sets the value of a private variable in the x instance
x.setName("Ben 2");

//Shouldn't have changed this : prints "candide"
y.publicFunction();

//Should have changed this : prints "Ben 2"
x.publicFunction();

JSFiddle: http://jsfiddle.net/northkildonan/kopj3dt3/1/


to podejście ma jeden ważny aspekt - jeśli utworzyłeś 2 obiekty, w pamięci będą 2 takie same metody (funkcja publiczna np.) 1000 obiektów zje całą twoją pamięć.
Artem G

2

Wzorzec modułu jest poprawny w większości przypadków. Ale jeśli masz tysiące instancji, klasy oszczędzają pamięć. Jeśli problemem jest oszczędzanie pamięci, a twoje obiekty zawierają niewielką ilość prywatnych danych, ale mają wiele funkcji publicznych, to chcesz, aby wszystkie funkcje publiczne działały w .prototypie, aby oszczędzać pamięć.

Oto co wymyśliłem:

var MyClass = (function () {
    var secret = {}; // You can only getPriv() if you know this
    function MyClass() {
        var that = this, priv = {
            foo: 0 // ... and other private values
        };
        that.getPriv = function (proof) {
            return (proof === secret) && priv;
        };
    }
    MyClass.prototype.inc = function () {
        var priv = this.getPriv(secret);
        priv.foo += 1;
        return priv.foo;
    };
    return MyClass;
}());
var x = new MyClass();
x.inc(); // 1
x.inc(); // 2

Obiekt privzawiera właściwości prywatne. Jest dostępny poprzez funkcję publiczną getPriv(), ale ta funkcja zwraca, falsechyba że ją przejdziesz secret, i jest to znane tylko w głównym zamknięciu.


To symuluje członków chronionych, typy dziedziczące po nim mogą również uzyskiwać dostęp do członków chronionych. Preferuję ten wzorzec także prywatny
HMR

2

A co z tym?

var Restaurant = (function() {

 var _id = 0;
 var privateVars = [];

 function Restaurant(name) {
     this.id = ++_id;
     this.name = name;
     privateVars[this.id] = {
         cooked: []
     };
 }

 Restaurant.prototype.cook = function (food) {
     privateVars[this.id].cooked.push(food);
 }

 return Restaurant;

})();

Wyszukiwanie zmiennych prywatnych jest niemożliwe poza zakresem funkcji natychmiastowej. Nie ma powielania funkcji, oszczędzając pamięć.

Minusem jest to, że wyszukiwanie zmiennych prywatnych jest nieporęczne, privateVars[this.id].cookedjest śmiesznie pisać. Istnieje również dodatkowa zmienna „id”.


Pozostawi Restaurantjak undefinedbo nie masz nic powrocie z funkcji anonimowej.
user4815162342

Gdzie i jak? Zakładając, że odniesienie do utworzonej restauracji zostanie utracone, privateVars nie będzie mieć odniesienia do swojego właściciela. Wykres odniesienia jest acykliczny. czego mi brakuje?
Evan Leis,

W rzeczywistości jest to jedyna odpowiedź, która oprócz metod obsługuje prywatne własności. Tylko dwie kwestie zostały już odnotowane w odpowiedzi.
pishpish

Widzę wyciek pamięci: gdy instancja Restaurantzostała wyrzucona do pamięci , jej wartości pozostają w środku privateVars. W tym przypadku WeakMapmoże być dobrym zamiennikiem Array.
Gershom

2

Zawiń cały kod w funkcję anonimową: wtedy wszystkie funkcje będą prywatne, TYLKO funkcje dołączone do windowobiektu:

(function(w,nameSpacePrivate){
     w.Person=function(name){
         this.name=name;   
         return this;
     };

     w.Person.prototype.profilePublic=function(){
          return nameSpacePrivate.profile.call(this);
     };  

     nameSpacePrivate.profile=function(){
       return 'My name is '+this.name;
     };

})(window,{});

Użyj tego :

  var abdennour=new Person('Abdennour');
  abdennour.profilePublic();

SKRZYPCE


1

Wolę przechowywać prywatne dane w powiązanym WeakMap. Pozwala to zachować publiczne metody na prototypie, do którego należą. Jest to najwydajniejszy sposób radzenia sobie z tym problemem w przypadku dużej liczby obiektów.

const data = new WeakMap();

function Foo(value) {
    data.set(this, {value});
}

// public method accessing private value
Foo.prototype.accessValue = function() {
    return data.get(this).value;
}

// private 'method' accessing private value
function accessValue(foo) {
    return data.get(foo).value;
}

export {Foo};

0

Funkcje prywatne nie mogą uzyskać dostępu do zmiennych publicznych przy użyciu wzorca modułu


0

Ponieważ wszyscy publikowali tutaj swój własny kod, ja też to zrobię ...

Lubię Crockforda, ponieważ wprowadził w JavaScript prawdziwe wzorce obiektowe. Ale wpadł też na nowe nieporozumienie, „tamte”.

Dlaczego więc używa słowa „to = to”? Nie ma to w ogóle nic wspólnego z funkcjami prywatnymi. Ma to związek z funkcjami wewnętrznymi!

Ponieważ według Crockforda jest to błędny kod:

Function Foo( ) {
    this.bar = 0; 
    var foobar=function( ) {
        alert(this.bar);
    }
} 

Więc zasugerował zrobienie tego:

Function Foo( ) {
    this.bar = 0;
    that = this; 
    var foobar=function( ) {
        alert(that.bar);
    }
}

Tak jak powiedziałem, jestem całkiem pewien, że Crockford się mylił, wyjaśniając to i to (ale jego kod jest z pewnością poprawny). A może po prostu oszukiwał świat Javascript, aby wiedzieć, kto kopiuje jego kod? Nie wiem ... Nie jestem maniakiem przeglądarki; D

EDYTOWAĆ

Ach, o to w tym wszystkim chodzi: Co to znaczy „var that = this;” znaczy w JavaScript?

Więc Crockie naprawdę nie miał racji w swoim wyjaśnieniu ... ale miał rację w swoim kodzie, więc nadal jest świetnym facetem. :))


0

Ogólnie dodałem tymczasowo obiekt prywatny _ do obiektu. Musisz dokładnie otworzyć prywatność w „Konstruktorze mocy” dla metody. Jeśli wywołasz metodę z prototypu, będziesz mógł zastąpić metodę prototypu

  • Udostępnij publiczną metodę w „Konstruktorze zasilania”: (ctx to kontekst obiektu)

    ctx.test = GD.Fabric.open('test', GD.Test.prototype, ctx, _); // is a private object
  • Teraz mam tę otwartą prywatność:

    GD.Fabric.openPrivacy = function(func, clss, ctx, _) {
        return function() {
            ctx._ = _;
            var res = clss[func].apply(ctx, arguments);
            ctx._ = null;
            return res;
        };
    };

0

Oto co wypracowałem:

Potrzebuje jednej klasy kodu cukru, który można znaleźć tutaj . Obsługuje również chronione, spadkowe, wirtualne, statyczne rzeczy ...

;( function class_Restaurant( namespace )
{
    'use strict';

    if( namespace[ "Restaurant" ] ) return    // protect against double inclusions

        namespace.Restaurant = Restaurant
    var Static               = TidBits.OoJs.setupClass( namespace, "Restaurant" )


    // constructor
    //
    function Restaurant()
    {
        this.toilets = 3

        this.Private( private_stuff )

        return this.Public( buy_food, use_restroom )
    }

    function private_stuff(){ console.log( "There are", this.toilets, "toilets available") }

    function buy_food     (){ return "food"        }
    function use_restroom (){ this.private_stuff() }

})( window )


var chinese = new Restaurant

console.log( chinese.buy_food()      );  // output: food
console.log( chinese.use_restroom()  );  // output: There are 3 toilets available
console.log( chinese.toilets         );  // output: undefined
console.log( chinese.private_stuff() );  // output: undefined

// and throws: TypeError: Object #<Restaurant> has no method 'private_stuff'

0
Class({  
    Namespace:ABC,  
    Name:"ClassL2",  
    Bases:[ABC.ClassTop],  
    Private:{  
        m_var:2  
    },  
    Protected:{  
        proval:2,  
        fight:Property(function(){  
            this.m_var--;  
            console.log("ClassL2::fight (m_var)" +this.m_var);  
        },[Property.Type.Virtual])  
    },  
    Public:{  
        Fight:function(){  
            console.log("ClassL2::Fight (m_var)"+this.m_var);  
            this.fight();  
        }  
    }  
});  

https://github.com/nooning/JSClass


0

Stworzyłem nowe narzędzie, które pozwala mieć prawdziwe prywatne metody na prototypie https://github.com/TremayneChrist/ProtectJS

Przykład:

var MyObject = (function () {

  // Create the object
  function MyObject() {}

  // Add methods to the prototype
  MyObject.prototype = {

    // This is our public method
    public: function () {
      console.log('PUBLIC method has been called');
    },

    // This is our private method, using (_)
    _private: function () {
      console.log('PRIVATE method has been called');
    }
  }

  return protect(MyObject);

})();

// Create an instance of the object
var mo = new MyObject();

// Call its methods
mo.public(); // Pass
mo._private(); // Fail

1
Czy możesz wyjaśnić, jak to działa? Jak / gdzie mogą Państwo nazwać _privatemetoda?
Bergi,

0

Musisz zamknąć faktyczną funkcję konstruktora, w której możesz zdefiniować swoje prywatne metody. Aby zmienić dane instancji za pomocą tych prywatnych metod, musisz dać im to „to”, albo jako argument funkcji, albo przez wywołanie tej funkcji za pomocą .apply (this):

var Restaurant = (function(){
    var private_buy_food = function(that){
        that.data.soldFood = true;
    }
    var private_take_a_shit = function(){
        this.data.isdirty = true;   
    }
    // New Closure
    function restaurant()
    {
        this.data = {
            isdirty : false,
            soldFood: false,
        };
    }

    restaurant.prototype.buy_food = function()
    {
       private_buy_food(this);
    }
    restaurant.prototype.use_restroom = function()
    {
       private_take_a_shit.call(this);
    }
    return restaurant;
})()

// TEST:

var McDonalds = new Restaurant();
McDonalds.buy_food();
McDonalds.use_restroom();
console.log(McDonalds);
console.log(McDonalds.__proto__);

Właściwie to nie działa. Każdy new Restaurantbędzie miał własnego restaurantkonstruktora, a „prototyp” będzie całkowicie nadużywany.
Bergi,

@Bergi. Właściwie masz rację. To by działało, ale byłoby również wieprzem zasobów (czy tak się nazywa?). Zredagowałem moją odpowiedź na ten temat.
Flex Elektro Deimling,

Dziękuję za aktualizację. Nie mam pojęcia, jak nazwać poprzednią wersję (ale „błąd” :-)
Bergi,

0

Wiem, że jest trochę za późno, ale co powiesz na to?

var obj = function(){
    var pr = "private";
    var prt = Object.getPrototypeOf(this);
    if(!prt.hasOwnProperty("showPrivate")){
        prt.showPrivate = function(){
            console.log(pr);
        }
    }    
}

var i = new obj();
i.showPrivate();
console.log(i.hasOwnProperty("pr"));

0

Na to pytanie jest już wiele odpowiedzi, ale nic nie odpowiada moim potrzebom. Więc wymyśliłem własne rozwiązanie, mam nadzieję, że będzie przydatne dla kogoś:

function calledPrivate(){
    var stack = new Error().stack.toString().split("\n");
    function getClass(line){
        var i = line.indexOf(" ");
        var i2 = line.indexOf(".");
        return line.substring(i,i2);
    }
    return getClass(stack[2])==getClass(stack[3]);
}

class Obj{
    privateMethode(){
        if(calledPrivate()){
            console.log("your code goes here");
        }
    }
    publicMethode(){
        this.privateMethode();
    }
}

var obj = new Obj();
obj.publicMethode(); //logs "your code goes here"
obj.privateMethode(); //does nothing

Jak widać, ten system działa, gdy używa się tego typu klas w javascript. O ile się zorientowałem, żadna z wyżej wymienionych metod nie działała.


1
Ciekawe: czy naprawdę trzeba było ujawnić tę funkcję, ale sprawić, by nie działała w środowisku uruchomieniowym - zamiast ukrywać ją przed dzwoniącymi, jak robią to wszystkie inne odpowiedzi? Jeśli tak, to dlaczego? Jak myślisz, jakie korzyści przyniesie to podejście? Wydaje mi się, że jest to po prostu niepotrzebny narzut wydajnościowy, niejasny interfejs API i, no cóż, zapewne spowoduje debugowanie piekła, ale zawsze jestem otwarty na nowe perspektywy ...
JHH

2
@ JHH, szczerze mówiąc, patrzę na to z bladością. Koszty ogólne na ogół nie są tego warte, chociaż dla mnie nie miało to większego znaczenia, ponieważ nie dzwoniłem do tych klas. Powodem, dla którego to zrobiłem, jest to, że jest stosunkowo czysty w sposobie pisania i wywoływania funkcji. Do tego czasu nie rozumiałem symboli, ale teraz, kiedy to rozumiem, myślę, że ogólnie jest to właściwy sposób, gdy korzystam z zajęć. Rozważam usunięcie tej odpowiedzi razem. Wysłałem kilka głupich odpowiedzi, ale hej, żyjesz i się uczysz.
thegunmaster

Dzięki za opinie! Nie byłem pewien, czy coś źle zrozumiałem. Ale tak, wszyscy żyjemy i uczymy się!
JHH

0

Zobacz tę odpowiedź, aby uzyskać czyste i proste rozwiązanie „klasowe” z prywatnym i publicznym interfejsem oraz obsługą kompozycji

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.