3

In JavaScript: The Good Parts, Crockford argues that one of the downsides of using the pseudo-classical pattern of inheritance is that it publicly exposes instance variables.

For example:

var Ball = function(width, color) {
  this.width = width;
  this.color = color;
}

var redBall = new Ball(5, "red");
redBall.width = 12; // Changes width to 12

Now, what if I want the width of the ball to be private?

Here's what I've tried:

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

var redBall = new Ball(5, "red");

Problem with that is we can still change this.getWidth and there may be prototype methods that rely on it.

What about ...

var Ball = function(width, color) {
  return {
    getWidth: function() { return width; },
    color: color
  }
}

var redBall = new Ball(5, "red");

Problem with that is that the prototype methods no longer have access to the instance variables. It's also closer to the functional pattern of inheritance, but with more indirection using the new operator.

So how do I achieve privacy using the pseudo-classical inheritance pattern? Is this even possible?

linstantnoodles
  • 4,350
  • 1
  • 22
  • 24
  • I'm not a JavaScript expert, but can't you just declare a `var width` inside the `Ball` constructor function? – Yuck Feb 15 '14 at 15:15
  • @Yuck Yeah except the prototype methods won't be able to access `width` then. – linstantnoodles Feb 15 '14 at 15:17
  • 4
    just curious why do you care if it's private or not? – Brian McGinity Feb 15 '14 at 15:23
  • @BrianMcGinity In this example or in general? I guess in this example it's not obvious why having `width` be private even matters, I'm just using it as an example. In general, privacy is crucial for protecting an object from unintended changes. – linstantnoodles Feb 15 '14 at 15:31
  • So you want only the public prototypes to have access to width...so coders are forced to use the prototype to change the value. – Brian McGinity Feb 15 '14 at 15:58
  • 1
    I think Brian has a point, crucial you say. I have not yet encountered such a situation. Your public api is well documented and your implementation members have the underscore naming convention. Code is better testable and doesn't assume someone using it is a complete idiot that should be completely locked out of your implementation because they can't read the documentation or suspect the name suggest not touching this particular member – HMR Feb 15 '14 at 15:59
  • @BrianMcGinity no they are using the prototype to read the value. Even if they are forced to used the prototype to change the value, they're still restricted in what they could change it to. – linstantnoodles Feb 15 '14 at 16:05
  • 1
    Warning about Crockford; I have not yet seen anything published by him where he correctly implements what he calls classical inheritance. Claims Object.create clones Parent constructor can't be re used in child and sets prototype of child with an instance of patent – HMR Feb 15 '14 at 16:06
  • @instantnoodles, and you do not want to use var along with a privileged member function? like `var width=width; this.getWidth=function(){return width}` – Brian McGinity Feb 15 '14 at 16:11
  • 1
    Prefix 'private' variables with an underscore, and assume that everything that starts with an underscore is 'private'. Some people argue that this doesn't prevent modification; that's true, but this should never be a problem if you're dealing with even remotely mature & competent programmers... – Martin Tournoij Feb 15 '14 at 16:22
  • I've added a pattern that has instance specific **protected** members that can be accessed from prototype functions in my answer. – HMR Feb 16 '14 at 02:34

2 Answers2

4

To answer your question; the only way to have intace specific private members is to have both the members and privileged functions (functions that can access them) in the same scope. This means they all have to be in the constructor body (var my private...this.myPrivileged=function(){ console.log (myPrivate...) or in a IIFE with a closure object keeping track of instances and their privates.

When returning an object private you already loose privacy because the calling code can mutate your private value. To prevent this you have to deep copy the value and return that.

To have privates on the prototype the privates will be shared. This because the instance is not known when declaring your prototype and private members.

This is because JavaScript doesn't have a private modifier and only simulates them through closures.

One pattern that can use prototype for instance specific protected variables is using Crockford's box example.

All protecteds are put in a box that can only be opened with a key, the key is available through closures to all prototype members defined in the IIFE.

Because the instance isn't known when creating prototype you have to invoke initProtecteds from the instance in order to create instance specific protected members.

Minimal code with an example protected instance member called medicalHistory is used in 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());
Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
2

This is interesting to consider.

To me (and I consider myself a student of js), it looks like only private member functions have access to the object's private variables. This is because it creates a closure around the var:

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

So programmers can do:

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

  redBall.specialWidthCalc(3);
  consoloe.log( redBall.getWidth() );

I am unable to create a prototype which has access to width.

Brian McGinity
  • 5,777
  • 5
  • 36
  • 46
  • Brian, you are correct. Maybe you find the following about constructor functions and prototype helpful http://stackoverflow.com/a/16063711/1641941 no patterns for privates though – HMR Feb 15 '14 at 16:28
  • Just for kicks I've tried to come up with a pattern for private members again but can only figure out how to implement protected. Updated my answer with the pattern. – HMR Feb 16 '14 at 02:42
  • 1
    Seems like a lot of unnecessary code for little gain. – Brian McGinity Feb 16 '14 at 15:37
  • I fully agree with you. Have not seen a compelling reason to use privates or protected in JavaScript. Other than your code looking more compelling and another module accidentally accessing or using your modules implementation members. – HMR Feb 17 '14 at 00:11