Create a class with IIFE that isn't a referenc

2019-01-27 03:25发布

问题:

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?

回答1:

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://stackoverflow.com/a/16063711/1641941



回答2:

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


回答3:

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