Herencia pseudo-clásica con privacidad?

En JavaScript: The Good Parts , Crockford argumenta que uno de los inconvenientes de usar el patrón pseudoclásico de herencia es que expone públicamente variables de instancia.

Por ejemplo:

var Ball = function(width, color) { this.width = width; this.color = color; } var redBall = new Ball(5, "red"); redBall.width = 12; // Changes width to 12 

Ahora, ¿y si quiero que el ancho de la pelota sea privado?

Esto es lo que he intentado:

 var Ball = function(width, color) { this.getWidth = function() { return width; } this.color = color; } var redBall = new Ball(5, "red"); 

El problema con eso es que todavía podemos cambiar this.getWidth y puede haber métodos prototipo que dependan de él.

Qué pasa …

 var Ball = function(width, color) { return { getWidth: function() { return width; }, color: color } } var redBall = new Ball(5, "red"); 

El problema con eso es que los métodos prototipo ya no tienen acceso a las variables de instancia. También está más cerca del patrón funcional de herencia, pero con más indirección usando el new operador.

Entonces, ¿cómo logro privacidad usando el patrón de herencia pseudo-clásico? ¿Esto es posible?

Para responder tu pregunta; la única manera de tener miembros privados específicos de intace es tener tanto los miembros como las funciones privilegiadas (funciones que pueden acceder a ellos) en el mismo ámbito. Esto significa que todos tienen que estar en el cuerpo del constructor (var mi private … this.myPrivileged = function () {console.log (myPrivate …) o en un IIFE con un objeto de cierre manteniendo un registro de las instancias y sus partes privadas .

Cuando devuelve un objeto privado, ya pierde privacidad porque el código de llamada puede mutar su valor privado. Para evitar esto, debe copiar profundamente el valor y devolverlo.

Para tener partes privadas en el prototipo, las partes privadas serán compartidas. Esto porque la instancia no se conoce al declarar su prototipo y miembros privados.

Esto se debe a que JavaScript no tiene un modificador privado y solo los simula mediante cierres.

Un patrón que puede usar prototipos, por ejemplo, variables protegidas específicas, es el uso del ejemplo de cuadro de Crockford.

Todas las protecciones se colocan en un recuadro que solo se puede abrir con una clave; la clave está disponible mediante cierres para todos los miembros de prototipo definidos en el IIFE.

Como no se conoce la instancia al crear el prototipo, debe invocar a initProtecteds desde la instancia para crear miembros protegidos específicos de la instancia.

El código mínimo con un miembro ejemplar protegido llamado medicalHistory se usa en Animal.

 function makeBox(key){ var ret = {}; return { get : function(pKey){ if(pKey===key){ return ret; } return false; } } }; var Person = function(args){ args = args || {}; this.name = args.name || "Nameless Person"; this.initProtecteds(); }; //using IIFE to define some members on Person.prototype // these members and only these members have access to // the passed object key (through closures) // later the key is used to create a box for each instance // all boxes use the same key so instances of same type // can access each other's protected members and instances // inheriting from Person can do so too, extending parent methods // will be trickier, no example for that is given in this code (function(key){ //private shared member var privateBehavior = function(instance,args){ //when you invoke this from public members you can pass // the instance or use call/apply, when using call/apply // you can refer to this as the current instance, when // passing it as an argument then instance will // be the current instance console.log("private shared invoked"); }; //set default _protecteds to false so init knows // it has not been initialised and needs to be shadowed // with a box Person.prototype._protecteds=false; Person.prototype.getMedicalHistory = function(){ //Maybe run some code that will check if you can access // medical history, invoking a private method privateBehavior(this,{}); var protectedObject = this._protecteds.get(key); //if medicalHistory is an object the calling code // can now mutate it return protectedObject.medicalHistory; }; Person.prototype.hasSameDesease = function(person){ //this Person instance should be able to see // medical history of another Person instance return person._protecteds.get(key); }; Person.prototype.getArr = function(){ //Returns protecteds.get(key).arr so we can // mutate it and see if protecteds are instance // specific return this._protecteds.get(key).arr; }; Person.prototype.initProtecteds = function(){ //only create box if it hasn't been created yet if(this._protecteds!==false) return; //use the same key for all instance boxes, one instance // can now open another instance's box this._protecteds=makeBox(key); //retreive the object held by the box var protectedObject = this._protecteds.get(key); //add protected members by mutating the object held // by the box protectedObject.medicalHistory = "something"; protectedObject.arr = []; //protectedObject is no longer needed protectedObject=null; }; }({})); var Animal = function(){ this.initProtecteds(); }; (function(key){ Animal.prototype._protecteds=false; Animal.prototype.initProtecteds = function(){ if(this._protecteds!==false) return; this._protecteds=makeBox(key); var protectedObject = this._protecteds.get(key); protectedObject.medicalHistory = "something"; }; }({})); var Employee = function(args){ //re use Person constructor Person.call(this,args); }; //set up prototype part of inheritance Employee.prototype = Object.create(Person.prototype); //repair prototype.constructor to point to the right function Employee.prototype.constructor = Employee; var ben = new Person({name:"Ben"}); var nameless = new Person(); console.log(ben.getMedicalHistory());//=something //key is in closure and all privileged methods are in that closure // since {} !== {} you can't open the box unless you're in the closure // or modify the code/set a breakpoint and set window.key=key in the closure console.log(ben._protecteds.get({}));//=false //One Person instance can access another instance's protecteds // Objects that inherit from Person are same console.log(ben.hasSameDesease(nameless));//=Object { medicalHistory="something"} var lady = new Animal(); //An Animal type object cannot access a Person protected members console.log(ben.hasSameDesease(lady));//=false var jon = new Employee({name:"Jon"}); console.log(ben.hasSameDesease(jon));//=Object { medicalHistory="something"} //making sure that protecteds are instance specific ben.getArr().push("pushed in ben"); console.log(jon.getArr()); console.log(nameless.getArr()); console.log(ben.getArr()); 

Esto es interesante de considerar.

Para mí (y me considero un estudiante de js), parece que solo las funciones de miembros privados tienen acceso a las variables privadas del objeto. Esto se debe a que crea un cierre alrededor de la var:

 var Ball = function(width, color) { var width = width; this.color = color; this.getWidth=function(){return width} this.specialWidthCalc=function(x){ width = width + x;} } 

Entonces los progtwigdores pueden hacer:

  var redBall = new Ball(5, "red"); consoloe.log( redBall.getWidth() ); redBall.specialWidthCalc(3); consoloe.log( redBall.getWidth() ); 

No puedo crear un prototipo que tenga acceso al ancho.