Métodos privados de JavaScript

482

Para hacer una clase de JavaScript con un método público, haría algo como:

function Restaurant() {}

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

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

De esa forma, los usuarios de mi clase pueden:

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

¿Cómo creo un método privado que pueda ser llamado por los métodos buy_foody use_restroompero no externamente por los usuarios de la clase?

En otras palabras, quiero que la implementación de mi método pueda hacer:

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

Pero esto no debería funcionar:

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

¿Cómo lo defino private_stuffcomo un método privado para que ambos sean válidos?

He leído el escrito de Doug Crockford varias veces, pero no parece que los métodos "privados" puedan llamarse mediante métodos públicos y los métodos "privilegiados" puedan llamarse externamente.

Wayne Kao
fuente

Respuestas:

403

Puedes hacerlo, pero la desventaja es que no puede ser parte del prototipo:

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();
    }
}
17 de 26
fuente
99
Ocultar el interior del cierre no garantizará la privacidad de todos los intérpretes. Ver code.google.com/p/google-caja/wiki/…
Mike Samuel
51
@mikesamuel - cierto, pero sólo cuando los intérpretes tienen errores en ellos :)
jvenema
133
Este es un método privado, pero tenderá a utilizar mucha más memoria que un método prototipo habitual, especialmente si está creando muchos de estos objetos. Para cada instancia de objeto, crea una función separada vinculada al objeto y no a la clase. Además, esto no recoge la basura hasta que se destruye el objeto mismo.
Arindam
44
Si crea un objeto que McDonalds () hereda del restaurante () McDonalds no puede anular los métodos privados si los declara de esta manera. Bueno, en realidad puedes, pero no funcionará si algún otro método llama al privado, llamará a la versión original del método y tampoco podrás llamar al método padre. Hasta ahora no he encontrado una buena manera de declarar métodos privados que funcionen bien con la herencia. Esto y las implicaciones de rendimiento hacen que este no sea un muy buen patrón de diseño. Recomendaría hacer algún tipo de prefijo para indicar métodos privados, comenzar con un subrayado.
Hoffmann
68
Se supone que los métodos privados no se anulan, son privados.
17 de 26
163

Uso de la función de invocación automática y llamada

JavaScript usa prototipos y no tiene clases (o métodos para el caso) como lenguajes orientados a objetos. Un desarrollador de JavaScript necesita pensar en JavaScript.

Cita de Wikipedia:

A diferencia de muchos lenguajes orientados a objetos, no hay distinción entre una definición de función y una definición de método. Más bien, la distinción ocurre durante la llamada a la función; cuando se llama a una función como método de un objeto, la palabra clave local de la función está vinculada a ese objeto para esa invocación.

Solución que utiliza una función de invocación automática y la función de llamada para llamar al "método" privado:

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

La función de llamada nos permite llamar a la función privada con el contexto apropiado ( this).


Más simple con Node.js

Si está utilizando node.js , no necesita el IIFE porque puede aprovechar el sistema de carga del módulo :

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

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

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

exports.MyObject = MyObject;

Cargue el archivo

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

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


(experimental) ES7 con el operador Bind

El operador de enlace ::es una propuesta ECMAScript y se implementa en Babel ( etapa 0 ).

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

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

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

Cargue el archivo

import MyObject from './MyObject';

let myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // TypeError: myObject.privateFun is not a function
Yves M.
fuente
34
Esta es la mejor respuesta. Los beneficios de los métodos privados, no basura.
TaylorMac
1
@TaylorMac Excepto por la .callparte.
pishpish
1
@janje ¿Eh? Ese es el punto de la pregunta, no hay privado f()en javascript (no en este momento).
Yves M.
2
@YvesM. El punto de la pregunta es elegir el mejor patrón que lo simule.
pishpish
1
@changed ¿cuál es el mejor compromiso para tener funciones ocultas (invocables desde el exterior), sintaxis agradable (no .call) y memoria pequeña (no hay funciones en la instancia)? ¿Eso incluso existe?
Ciprian Tomoiagă 01 de
161

Puede simular métodos privados como este:

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();

Más información sobre esta técnica aquí: http://webreflection.blogspot.com/2008/04/natural-javascript-private-methods.html

georgebrock
fuente
77
También sugeriría el sitio de Douglas Crockford como un recurso sobre métodos privados / públicos y miembros javascript.crockford.com/private.html
Jared
10
Mencionó ese enlace en la pregunta.
Gulzar Nazim
8
La desventaja de este método es que no se puede tener acceso privado a otros datos privados en Restaurant_stuff () y otros métodos de Restaurant no pueden llamar a private_stuff (). Lo bueno es que si no necesita ninguna de las condiciones que acabo de mencionar, puede mantener use_restroom () en el prototipo.
17 de 26
66
Esta debería ser la solución y la respuesta aceptada porque el autor está usando claramente la propiedad prototipo.
Gabriel Llamas
23
Con el patrón propuesto por @georgebrock, todos los datos privados se compartirán entre todos los objetos del restaurante. Eso es similar a las variables y funciones privadas estáticas en OOP basado en clases. Supongo que el OP no quiere esto. Para ilustrar lo que quiero decir, creé un jsFiddle .
feklee
35

En estas situaciones cuando tiene una API pública, y desea métodos / propiedades privadas y públicas, siempre uso el Patrón de módulo. Este patrón se hizo popular dentro de la biblioteca YUI, y los detalles se pueden encontrar aquí:

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

Es realmente sencillo y fácil de comprender para otros desarrolladores. Por un simple ejemplo:

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

fuente
este tipo de cosas no serían detectadas por el autocompletado de un IDE :(
Haga clic en Upvote
19
Pero esto no es una clase, por lo que no puede tener 2 "instancias" de esto con diferentes estados.
DevAntoine
elimine la parte () y tendrá una "clase". al menos donde puedes instanciar diferentes instancias con diferentes estados. aunque el patrón del módulo requiere bastante memoria ...
oligofren
@DevAntoine Mira los comentarios para la respuesta de 17 de 26. En JavaScript, las clases extensibles y los métodos privados no van fácilmente de la mano. Mi sugerencia en este caso sería ir con la composición sobre la herencia. Cree un prototipo extensible con los mismos métodos que el objeto concreto cerrado. Luego, dentro de su prototipo, puede decidir cuándo llamar a los métodos en su objeto concreto.
¿Hay alguna desventaja en llamar a las variables públicas desde las funciones privadas de la siguiente manera aPrivateMethod = function() { MYLIB.aPublicProperty}:?
Hanna
21

Aquí está la clase que creé para comprender lo que Douglas Crockford ha sugerido en su sitio Miembros privados en 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

}
Sarath
fuente
55
El that = este es un patrón que es bastante común, popularizado por Crockford antes mencionado en su libro "Javascript: Las partes buenas"
oligofren
8
thatse usa en lugar de thisevitar problemas de alcance, cuando las funciones están vinculadas a un objeto diferente. Aquí, usted está almacenando thisen thaty nunca usarlo otra vez, que es lo mismo que no hacerlo en absoluto. Debe cambiar thiscon thattodas las Constructorfunciones internas (no la declaración de métodos). Si employeese applyed o called de alguna manera, estos métodos podrían arrojar ya thisque se vincularán incorrectamente.
Maroshii
Además, cada instancia tendrá una copia completa de las funciones privadas, ineficiente. Además del hecho de que los métodos públicos no pueden acceder a los vars de clase privada, me dan ganas de cambiar a dardo. Lamentablemente angulardart es super beta.
Ray Foss
¿Dónde está el método "constructor" en esto? ¿Dónde pondría la lógica que normalmente se ejecutaría en el método constructor de una clase cuando se instancia?
BadHorsie
13

Conjuré esto: EDITAR: En realidad, alguien se ha vinculado a una solución idéntica. Duh!

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!'

fuente
1
Esta es una buena técnica, pero ¿cómo permitiría parámetros en su constructor? Por ejemplo, var getAwayVehicle = new Car(100);dónde 100está la velocidad y desea alertar la velocidad. ¡Gracias!
Jason
1
Lo descubrí, puede tener var Car = function(speed) { this.speed = speed; }y `return {constructor: Car, ...`
Jason
11

Creo que esas preguntas surgen una y otra vez debido a la falta de comprensión de los cierres. Сlosures es lo más importante en JS. Cada programador de JS tiene que sentir la esencia de esto.

1. Primero que nada necesitamos hacer un alcance separado (cierre).

function () {

}

2. En esta área, podemos hacer lo que queramos. Y nadie lo sabrá.

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. Para que el mundo sepa sobre nuestra clase de restaurante, tenemos que devolverla del cierre.

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

4. Al final, tenemos:

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. Además, este enfoque tiene potencial para heredar y crear plantillas

// 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')

Espero que esto ayude a alguien a entender mejor este tema

imos
fuente
2
¡Lo que tienes en el punto 4 es maravilloso! Creo que es la única respuesta de todas las aquí donde obtienes tanto el rendimiento / ganancias de memoria del uso de métodos en el prototipo Y estos métodos públicos tienen acceso completo a los miembros privados. +1
Hudon
77
No funciona La variable de nombre aquí actúa como variable estática y es compartida por todas las instancias de Restaurant. Aquí está jsbin: jsbin.com/oqewUWa/2/edit?js,output
Shital Shah
es un buen intento, pero como señaló Shital, la variable de nombre tiene errores.
oligofren
2
agregar mi 2c aquí para reafirmar que esto no funciona, se ve bien, pero como se señaló anteriormente "nombre" servirá como una variable estática, es decir, compartida en todas las instancias
Paul Carroll
10

Personalmente, prefiero el siguiente patrón para crear clases en 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(); }
        ...
    };
})();

Como puede ver, le permite definir propiedades de clase y propiedades de instancia, cada una de las cuales puede ser pública y privada.


Manifestación

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();

Ver también este violín .

John Slegers
fuente
Esto me da ganas de usar las clases de es6 y ver lo que transpira también
sheriffderek
9

Todo este cierre te costará. Asegúrese de probar las implicaciones de velocidad, especialmente en IE. Encontrará que está mejor con una convención de nomenclatura. Todavía hay muchos usuarios corporativos que están obligados a usar IE6 ...

Kelly
fuente
34
¿A quién le importa, en serio?
nowayyy
17
Ese 9% que todavía usa IE6 no se preocupa por la velocidad, las optimizaciones y todas las características modernas de HTML5. Entonces los cierres no costarán nada.
Gabriel Llamas
66
Ahora es 0.5% (agosto de 2012) w3schools.com/browsers/browsers_explorer.asp
Lorenzo Polidori
77
@LorenzoPolidori w3schools users! == usuarios web corporativos;]
WynandB
Una convención de nomenclatura (por ejemplo, anteponer un guión bajo) es el camino a seguir. El código es más fácil de mantener y los métodos aún están definidos en el prototipo. Hoy en día, sin embargo ... acabo de marcar el método como privado en mecanografiado.
David Sherret
5

No seas tan detallado. Es Javascript. Use una convención de nomenclatura .

Después de años de trabajar en clases de es6, recientemente comencé a trabajar en un proyecto de es5 (usando requireJS que ya tiene un aspecto muy detallado). He estado una y otra vez todas las estrategias mencionadas aquí y básicamente todo se reduce a usar una convención de nomenclatura :

  1. Javascript no tiene palabras clave de alcance como private . Otros desarrolladores que ingresen a Javascript lo sabrán por adelantado. Por lo tanto, una simple convención de nomenclatura es más que suficiente. Una simple convención de nomenclatura de prefijos con un guión bajo resuelve el problema de las propiedades privadas y los métodos privados.
  2. Aprovechemos el prototipo por razones de velocidad, pero no seamos más detallados que eso. Intentemos mantener la "clase" de es5 tan cercana a lo que podríamos esperar en otros lenguajes de fondo (y tratar cada archivo como una clase, incluso si no necesitamos devolver una instancia).
  3. Demostremos con una situación de módulo más realista (usaremos es5 antiguos y requireJs antiguos).

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();
            }
            */
        }
    });
martillo programador
fuente
2
Cabe señalar que ni el lenguaje Javascript ni ningún host de navegador típico define ningún objeto que se base en convenciones de nomenclatura para "ocultar" el estado privado, por lo que, si bien puede tener razón en que los desarrolladores comprenderán el concepto, todavía conduce a un Enfoque OO para la programación OO.
Rich Remer
¿Puedo pedir una buena referencia para hacer eso? Hay partes en este ejemplo que son nuevas para mí. El define, yy constructorla estructura misma. Si bien estoy de acuerdo principalmente con la respuesta, comencé a trabajar con JS con mucha influencia de OOP e incluso salté demasiado temprano a TS ya que tenía experiencia previa en C #. Creo que tengo que desaprender estas cosas y tengo que entender el paradigma de prototipos / procedimientos. (Votado, por cierto)
Cold Cerberus
1
@ColdCerberus este fragmento de código está utilizando es5. Puede ver una imagen completa de este enfoque aquí: gist.github.com/jonnyreeves/2474026 . Pero tenga en cuenta que querrá adoptar este enfoque y actualizarlo para usar clases es6 : googlechrome.github.io/samples/classes-es6 y módulos es6 (sintaxis de importación / exportación): hackernoon.com/…
prograhammer
5

Puede hacerlo ahora con los métodos privados es10 . Solo necesita agregar un #antes del nombre del método.

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

  getPrivateMessage() {
    return #privateMethod();
  }
}
D-Marc
fuente
2
Excepto que esta es la etapa 3 y aún no es oficialmente parte del lenguaje.
misterhtmlcss
3

Tome cualquiera de las soluciones que siguen el patrón privado o privilegiado de Crockford . Por ejemplo:

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

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

En cualquier caso en el que el atacante no tenga "ejecutar" directamente en el contexto JS, no tiene forma de acceder a ningún campo o método "público" o "privado". En caso de que el atacante tenga ese acceso, puede ejecutar esta línea:

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

Tenga en cuenta que el código anterior es genérico para todo constructor-type-privacy. Fallará con algunas de las soluciones aquí, pero debe quedar claro que casi todas las soluciones basadas en el cierre pueden romperse de esta manera con diferentesreplace() parámetros.

Después de que esto se ejecute, cualquier objeto creado con new Foo()tendrá un evalmétodo al que se puede llamar para devolver o cambiar valores o métodos definidos en el cierre del constructor, por ejemplo:

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

El único problema que puedo ver con esto es que no funcionará en los casos en que solo haya una instancia y se cree en carga. Pero entonces no hay ninguna razón para definir realmente un prototipo y en ese caso el atacante puede simplemente recrear el objeto en lugar del constructor, siempre que tenga una forma de pasar los mismos parámetros (por ejemplo, son constantes o se calculan a partir de los valores disponibles).

En mi opinión, esto hace que la solución de Crockford sea inútil.Dado que la "privacidad" se rompe fácilmente, las desventajas de su solución (menor legibilidad y facilidad de mantenimiento, menor rendimiento, mayor memoria) hacen que el método basado en el prototipo "sin privacidad" sea la mejor opción.

Usualmente uso guiones bajos para marcar __privatey _protectedmétodos y campos (estilo Perl), pero la idea de tener privacidad en JavaScript solo muestra cómo es un lenguaje incomprendido.

Por lo tanto, no estoy de acuerdo con Crockford excepto por su primera oración.

Entonces, ¿cómo se obtiene privacidad real en JS? Ponga todo lo que se requiere para ser privado en el lado del servidor y use JS para hacer llamadas AJAX.

Fozi
fuente
Este es un problema grave que debería ser más conocido. ¿Hay una 'defensa' contra este ataque?
James
@ James Ninguno que yo sepa, creo que es la naturaleza de la bestia. Como señalé, puede mover la funcionalidad al servidor donde se ejecuta en un entorno protegido. Sin embargo, el punto que quería superar en mi respuesta es que la solución de Crockford no está ayudando, complica innecesariamente el código y oculta la necesidad de hacer algo al respecto.
Fozi
Si un usuario ingresa una contraseña secreta, no puede hacer esto del lado del servidor. En algún momento la contraseña estará en una variable 'privada'. Entonces, ¿podría un atacante leerlo? Confío en mi código, y de todos modos los estándares de mi casa no permiten eval (). El atacante podría ser un complemento o biblioteca de JavaScript malicioso de terceros que no verifiqué correctamente, por lo que sí, tengo que verificarlos. El atacante también podría ser algo así como un anuncio en el lado que no debería interactuar con mi código. Protejo contra eso envolviendo todo mi código de forma anónima (function () {allMyStuff}());para no exponer nada global.
James
@ James Esto está recibiendo OT, si desea continuar esto, abra una nueva pregunta. Sí, un atacante puede leer la contraseña. De su variable "privada". O del DOM. O puede reemplazar la API AJAX. O reemplaza su página con otra cosa. Si no puede hacer nada de lo anterior, entonces no hay necesidad de "privacidad" de JS porque tampoco puede leer ninguna de sus variables de JS. El punto es que la "solución" de Crockford que todos están usando en este momento no está ayudando con este problema.
Fozi
Creo que la ofuscación de código pseudoaleatorio puede ser una defensa débil contra este ataque, más difícil de modificar el cuerpo de la función cuando no se puede depender de que la función tenga un nombre fijo; más difícil de hacer f.eval('nameOfVariable')cuando no sabes qué 'nameOfVariable'es ...
Gershom
2

Si desea la gama completa de funciones públicas y privadas con la capacidad de las funciones públicas de acceder a funciones privadas, codifique el diseño de un objeto como este:

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 () {
  };
}
domgblackwell
fuente
¿Alguien puede decirme por qué esto fue rechazado? Me parece bien.
thomasrutter
10
Cada vez que haces un new MyObject, el prototipo de MyObjectse reemplaza con los mismos valores.
bpierre
2
-1. Nunca asignes al .prototypeinterior del constructor.
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" );

Similar a georgebrock pero un poco menos detallado (en mi humilde opinión) ¿Algún problema para hacerlo de esta manera? (No lo he visto en ningún lado)

editar: me di cuenta de que esto es un poco inútil ya que cada instancia independiente tiene su propia copia de los métodos públicos, lo que socava el uso del prototipo.

David
fuente
2

Esto es lo que más he disfrutado hasta ahora con respecto a los métodos / miembros privados / públicos y la creación de instancias en javascript:

Aquí está el artículo: http://www.sefol.com/?p=1090

y aquí está el ejemplo:

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/

low_rents
fuente
Este enfoque tiene una cuestión importante: si ha creado 2 objetos, en la memoria habrá 2 mismos métodos (PublicFunction, por ejemplo). 1000 objetos consumirán toda su memoria.
Artem G
2

El patrón del módulo es correcto en la mayoría de los casos. Pero si tiene miles de instancias, las clases ahorran memoria. Si ahorrar memoria es una preocupación y sus objetos contienen una pequeña cantidad de datos privados, pero tienen muchas funciones públicas, entonces querrá que todas las funciones públicas vivan en el .prototype para ahorrar memoria.

Esto es lo que se me ocurrió:

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

El objeto privcontiene propiedades privadas. Es accesible a través de la función pública getPriv(), pero esta función regresa a falsemenos que la pase secret, y esto solo se conoce dentro del cierre principal.

James
fuente
Eso simula miembros protegidos, los tipos que heredan de él también pueden acceder a los miembros protegidos. Estoy a favor de este patrón sobre el privado también
HMR
2

¿Qué hay de esto?

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;

})();

La búsqueda de variables privadas es imposible fuera del alcance de la función inmediata. No hay duplicación de funciones, ahorrando memoria.

La desventaja es que la búsqueda de variables privadas es torpe, privateVars[this.id].cookedes ridículo escribir. También hay una variable "id" adicional.

Evan Leis
fuente
Esto saldrá Restaurantcomo undefinedporque no está devolviendo nada de la función anónima.
user4815162342
¿Dónde y cómo? Suponiendo que se pierde la referencia al restaurante creado, privateVars no tendrá una referencia a su propietario. El gráfico de referencia es acíclico. ¿Qué me estoy perdiendo?
Evan Leis
En realidad, esta es la única respuesta que admite propiedades privadas además de los métodos. Los dos únicos problemas ya están señalados en la respuesta.
pishpish
Veo una pérdida de memoria: cuando una instancia de Restaurantse ha recogido basura, sus valores permanecen dentro privateVars. A WeakMappuede ser un buen reemplazo para el Arrayen este caso.
Gershom
2

Envuelva todo el código en Función anónima: entonces, todas las funciones serán privadas, SOLO las funciones adjuntas al windowobjeto:

(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,{});

Utilizar este :

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

VIOLÍN

Abdennour TOUMI
fuente
1

Prefiero almacenar datos privados en un asociado WeakMap. Esto le permite mantener sus métodos públicos en el prototipo al que pertenecen. Esta parece ser la forma más eficiente de manejar este problema para un gran número de objetos.

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};
rico remer
fuente
0

Las funciones privadas no pueden acceder a las variables públicas utilizando el patrón de módulo

Dooma
fuente
0

Como todos estaban publicando aquí su propio código, yo también lo haré ...

Me gusta Crockford porque introdujo patrones orientados a objetos reales en Javascript. Pero también se le ocurrió un nuevo malentendido, el "ese".

Entonces, ¿por qué está usando "that = this"? No tiene nada que ver con funciones privadas en absoluto. Tiene que ver con funciones internas!

Porque según Crockford, este es un código con errores:

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

Entonces sugirió hacer esto:

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

Entonces, como dije, estoy bastante seguro de que Crockford se equivocó en su explicación sobre eso y esto (pero su código es ciertamente correcto). ¿O simplemente estaba engañando al mundo de Javascript, para saber quién está copiando su código? No sé ... no soy un geek del navegador; D

EDITAR

Ah, de eso se trata: ¿qué significa 'var that = this;' significa en JavaScript?

Entonces Crockie estaba realmente equivocado con su explicación ... pero con su código, así que sigue siendo un gran tipo. :))

marca
fuente
0

En general, agregué el objeto privado _ temporalmente al objeto. Debe abrir la privacidad exlipcitly en el "Power-constructor" para el método. Si llama al método desde el prototipo, podrá sobrescribir el método prototipo

  • Hacer accesible un método público en el "Constructor de energía": (ctx es el contexto del objeto)

    ctx.test = GD.Fabric.open('test', GD.Test.prototype, ctx, _); // is a private object
  • Ahora tengo este openPrivacy:

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

Esto es lo que resolví:

Necesita una clase de código de azúcar que puede encontrar aquí . También admite material protegido, herencia, virtual, estático ...

;( 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'

fuente
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

snowkid
fuente
0

He creado una nueva herramienta para permitirle tener verdaderos métodos privados en el prototipo https://github.com/TremayneChrist/ProtectJS

Ejemplo:

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
Trem
fuente
1
¿Puedes explicar cómo funciona, por favor? ¿Cómo / dónde puede llamar al _privatemétodo?
Bergi
0

Debe cerrar su función de constructor real, donde puede definir sus métodos privados. Para cambiar los datos de las instancias a través de estos métodos privados, debe darles "esto" con ellos, ya sea como un argumento de función o llamando a esta función con .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__);
Flex Elektro Deimling
fuente
En realidad, no funciona. Cada uno new Restauranttendrá su propio restaurantconstructor, y el "prototipo" es totalmente abusado.
Bergi
@Bergi. En realidad, tienes razón. Funcionaría, pero también sería un recurso de recursos (¿se llama así?). Edité mi respuesta con respecto a eso.
Flex Elektro Deimling
Gracias por la actualización. No tengo idea de cómo llamar a la versión anterior (pero "error" :-)
Bergi
0

Sé que es demasiado tarde, pero ¿qué tal esto?

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"));
Maxim Balaganskiy
fuente
0

Ya hay muchas respuestas sobre esta pregunta, pero nada satisfizo mis necesidades. Así que se me ocurrió mi propia solución, espero que sea útil para alguien:

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

Como puede ver, este sistema funciona cuando se usa este tipo de clases en JavaScript. Por lo que descubrí, ninguno de los métodos comentados anteriormente lo hizo.

thegunmaster
fuente
1
Curioso: ¿Realmente fue su necesidad exponer la función pero hacerla no operativa en tiempo de ejecución, en lugar de ocultarla a las personas que llaman externas como lo hacen todas / la mayoría de las otras respuestas? Si es así, ¿por qué? ¿Cuál considera que es el beneficio de este enfoque? Para mí, esto parece ser solo una sobrecarga de rendimiento innecesaria, una API poco clara y, bueno, probablemente causará un infierno de depuración, pero siempre estoy abierto a nuevas perspectivas ...
JHH
2
@JHH para ser honesto, estoy palmeando la cara al mirar esto. Los gastos generales generalmente no valen la pena, aunque para mí no importó demasiado, ya que no hice muchas llamadas a estas clases. La razón por la que lo hice de esta manera es porque es relativamente limpio en la forma en que escribe y llama a las funciones. No entendía los símbolos y tal por el momento, pero ahora que lo hago, creo que, en general, ese es el camino a seguir cuando se usan las clases. Estoy considerando eliminar esta respuesta por completo. He publicado varias respuestas estúpidas, pero bueno, vives y aprendes.
thegunmaster
¡Gracias por la respuesta! No estaba segura de haber entendido mal algo. Pero sí, ¡todos vivimos y aprendemos!
JHH
0

Vea esta respuesta para obtener una solución limpia y simple de 'clase' con una interfaz pública y privada y soporte para la composición

kofifus
fuente