Create a class with IIFE that isn't a reference?
Asked Answered
R

3

4

I'm new to JavaScript and I'm trying to wrap my head around creating "classes" with private data and public functions. I've been told Immediately Invoked Function Expressions (IIFE) accomplish this but when I "instantiate" new objects from the class they reference the private data instead of holding their own.

Some of this is borrowed from Create a JS class: IIFE vs return prototype

For example, a simple Car "class":

var Car = (function() {

    var body = { color: 'red' };
    Car.prototype.newColor = function(color) {
            body.color = color;
        };
    Car.prototype.getColor = function() {
            return body.color;
        };

    return Car;
})();

var car1 = new Car();
var car2 = new Car();

car2's color also gets changed to purple.

car1.newColor('purple');
car2.getColor(); // 'purple'

I want each object of the Car class to hold its own private data. How can this be accomplished with IFFE, or is there another way?

Ratal answered 9/11, 2013 at 16:35 Comment(1)
possible duplicate of javascript - accessing private member variables from prototype-defined functionsBillups
C
3

The only way to simulate private instance variables it to declare them as var myprivate in the constructor function.

Any privileged method (=method that can access the private member) has to be declared within the constructor function's body as well so can't be on the prototype (will cost you extra cpu and memory and maybe doesn't optimize as well in some JS engines).

I never had a situation where it was needed to do this since in my opinion the cost is not worth the gain. Usually indicate to my future self and other programmers that a member is private by a widely used naming convention (name starts with underscore) _myPrivate

"Public override"'s answer inspired me to create the following code. Private instance members can be accessed publicly by ben._data.set or you could re implement rules and or getters/setters so someone could still abuse it. It can still clean up you're object's publicly accessible members and making it easier to use the getters and setters.

//Namespacing DataStore to limit scope of the closures
var tools = {
  DataStore : function(){
    var store = [];
    this.get = function(key){
      return store[key];
    };
    this.set = function(key,value){
      store[key] = value;
      return value;
    };
  }
};
//Person constructor
var Person = function(name){
  //you can access this member directly
  // bob.name = "Lucy";
  this.name=name;
  //if having _data as not accesable by defining
  //  with var _data we whould have to define
  //  get and set here as this.get and this.set
  this._data=new tools.DataStore();
};
//constant value used to get or set, for example:
//ben.get(ben.AGE);
//Could add this and rules to Person instead of Person.prototype
//then you'll need a helper function to set up inheritance
//to make sure the static's on Person are copied to it's children
Person.prototype.AGE=0;
//rules for getters and setters
//Will be a problem with inheritance if on prototype 
//function Employee(name){Person.call(this,name);};
//Employee.prototype=Object.create(Person.prototype);
//Employee.prototype.rules["0set"]=..overwrites Person.prototype.rules["0set"]
//When inheriting you need to have a helper function set the rules for a child
//object
Person.rules = {}
//rule for AGE set
Person.rules[Person.prototype.AGE+"set"] = function(val){
  var tmp;
  tmp = parseInt(val);
  if(isNaN(tmp)){
    throw new Error("Cannot set the age of the person "+
      "to non number value, value of age:"+val);
  }
  if(tmp>150){
    throw new Error("Are you sure this is a person and "+
      "not a turtule? Trying to set age to:"+val);
  }
  return this._data.set(this.AGE,tmp);
};
//rule for age get
Person.rules[Person.prototype.AGE+"get"] = function(){
  return this._data.get(this.AGE);
};
Person.prototype.get = function(key){
  return Person.rules[key+"get"].call(this);
};
Person.prototype.set  = function(key,value){
  return Person.rules[key+"set"].call(this,value);
};

var ben = new Person("Ben");
ben.set(ben.AGE,22);
console.log(ben.get(ben.AGE));
try{
  ben.set(ben.AGE,151);
}catch(e){
  console.log("error",e);
}
try{
  ben.set(ben.AGE,"HELLO WORLD!");
}catch(e){
  console.log("error",e);
}

Note of caution: Person.rules needs to be copied to Child instances when you want to inherit from Person.

More about prototype, inheritance, overriding, calling super, multiple inheritance(mix in) and the value of this here: https://mcmap.net/q/24597/-prototypical-inheritance-writing-up-duplicate

Crustaceous answered 9/11, 2013 at 17:10 Comment(2)
your answer to https://mcmap.net/q/24597/-prototypical-inheritance-writing-up-duplicate really helped solidify classes and private variables. And swayed my decision for making this the answer.Ratal
@Crustaceous you deserve mad kudos. Third answer I've read by you and they've all enlightened me.Irregular
M
1

But that way you define .privilegedMethod() each time an object is created and each of them will hold diferent version of the ( same purpose ) method...

Solution I came up with is to use object to object ( private ) hashmap, and map the newly created object to it's corresponding data in ctor function, and use hasmap as 'manager' to figure out which data correspond to which object, in prototype methods, something like this:

var Car = 
( function ( hashmap ) {

  function PrivateClassCarData ( c, t ) {
    this.color = c;
    this.type  = t;
  }

  function Car ( color, type ) {
    hashmap.place( this, new PrivateClassCarData( color, type ) );
  }

  // read
  Car.prototype.getColor =
  function () {
    return hashmap.read( this ).color;
  };

  // write
  Car.prototype.setColor =
  function (c) {
    hashmap.read( this ).color = c;
    return this;
  };

  // weak point, memory leak source
  // dereference object from hash-map before updating variable that points to it
  // another reference is kept in hashmap
  // @TODO, automatic dereferencing execution, anybody?
  Car.prototype.mfree =
  function () {
    hashmap.drop( this );
    return this;
  };

  return Car;

} )(

  // basic hash-map implementation
  // maps objects to objects
  ( function ( hk, hv ) {

    return {

      place : function ( objKey, objVal ) {
        hk.push( objKey );
        hv.push( objVal );
        return this;
      },

      read  : function ( objKey ) {
        return hv[ hk.indexOf( objKey ) ];
      },

      drop  : function ( objKey ) {
        var pos;
        ( ( pos = hk.indexOf( objKey ) ) != -1 )
        && (
          hk.splice( pos, 1 ),
          hv.splice( pos, 1 )
        );
        return this;
      }

    };

  } )( [], [] )

);

var c1 = new Car("red","ferrary");
var c2 = new Car("white","porche");

c1.getColor();
// red

c2.setColor("silver");

c1.getColor();
// red

c2.getColor();
// silver
//
Mortify answered 9/11, 2013 at 18:7 Comment(3)
Nice one on the weak point comment. I ran into the same problem when creating a static method that keeps track of created instances. You'll have to explicitly call destroy or in your case mfree when you're done with the created instance or the hashmap prevents the instance being garbage collected. Other than having to create and destroy instances I could not find a solution for this as well. https://mcmap.net/q/213303/-how-to-access-variables-of-parent-object-within-function You certainly get my +1 as this could work if you have a lot of privileged methods.Crustaceous
yeah, and there is no way to access internal reference counting system of garbage collector in JavaScript as far as I know.Mortify
second thought, what if you you use diferent aproach, like .data() method of sort, use it to manipulate private data store, and by dereferencing the object JavaScript is able to recognize the circural reference made by object and .data() api and will gc them both when no outer references exist any more? will post the idea as soon as I get it.Mortify
M
1
var Car = 
( function ( cardb ) {

  function Car ( color ) {

    // facing the same problem here
    // reinstaling .data() method for each created object
    // but this way each has its own data store object
    // and inner 1 to 1 circular reference js is able to deal with
    cardb( this );

    // set provided color parameter
    this.data("color", color);

  }

  return Car;

} )(

  // function to install .data() method to given object
  // it gets attached to object directly, instead of
  // attaching it to .prototype, in which case all
  // object will access same data store
  function ( obj ) {

    var _data = {};

    obj.data =
    function ( name, value ) {
      return arguments.length
       ? (
        ( value == null )
         ? _data[name]
         : (
           _data[name] = value,
           this
         )
       )
       : _data;
    };

    return obj;

  }
);

var c1 = new Car("red");
var c2 = new Car("blue");

c1.data("color");
// red

c2.data("color");
// blue

c1.data("color","white");

c2.data("color");
// blue
c1.data("color");
// white
//
Mortify answered 9/11, 2013 at 19:19 Comment(1)
Nice, it you can implement getter and setter rules in the data method. I would still use _private for browser run code since the security aspect is not a big concern. Someone injecting script in my web application would unlikely misuse setting private variables. And other programmers should know better then to directly access _privates. JavaScript isn't always used on web pages though so there would be a use for it.Crustaceous

© 2022 - 2024 — McMap. All rights reserved.