Private Variables In JavaScript

Often times, many people have made claims that it is not possible to make variables private in JavaScript. I am here to tell you that those people are wrong. There are many implementations from JavaScript guru’s such as Douglas Crockford explaining how to do this. Although these implementations will get the job done, I have noticed that these implementations do not use prototypal functions. On the contrary, the following shows how to give prototypal functions access to private members:

// Immediately executed closure to define a person.
var Person = (function() {
  // The parameter required to retrieve the private data of a person object.
  var theKey = {};
  
  function Person(firstName, lastName, isMale, birthDate) {
    // If birth date is a string, convert it to a date.
    if(typeof birthDate == "string")
      birthDate = new Date(birthDate);
    if(isNaN(birthDate.getTime()) || !(birthDate instanceof Date))
       throw new Error("the passed birth date was invalid");
    // Object literal to keep track of the private data.
    var privateData = {
      firstName : firstName + "",
      lastName : lastName + "",
      isMale : !!isMale,
      birthDate : birthDate
    };
    // If theKey is passed as aKey, the private members of the object are
    // returned.  Since aKey must be theKey in order to retrieve privateData and
    // theKey is an empty object literal, you can be sure that private members
    // will not be returned from the function to any code outside of the
    // closure.  The only non-prototypal function.
    this._ = function(aKey) {
      if(aKey == theKey)
        return privateData;
    };
  }
  
  // Alias for the prototype of the Person object.
  var p = Person.prototype;
  
  // Get or set the first name for the person.
  p.firstName = function(newName) {
    // Get the private data members.
    var pData = this._(theKey);
    // If a parameter was passed, assign the new first name.
    if(!arguments.length)
      return pData.firstName;
    // Set the first name.
    pData.firstName = newName;
    // Allow chainable calls.
    return this;
  };
  
  // Get or set the last name for the person.
  p.lastName = function(newName) {
    // Get the private data members.
    var pData = this._(theKey);
    // If a parameter was passed, assign the new last name.
    if(!arguments.length)
      return pData.lastName;
    // Set the last name.
    pData.lastName = newName;
    // Allow chainable calls.
    return this;
  };
  
  // Gets how many years old the person is.
  p.yearsOld = function() {
    var now = new Date;  // current date
    var birth = this._(theKey).birthDate;  // birth date
    var years = now.getFullYear() - birth.getFullYear();  // difference in years
    now = now.getMonth() * 100 + now.getDate();  // now as MMDD as number
    birth = birth.getMonth() * 100 + birth.getDate();  // now as MMDD as number
    return years - (now < birth);  // Return real years old.
  };
  
  // Gets the birth date.  Makes valueOf an alias so that people can be compared
  // by age.
  p.birthDate = p.valueOf = function() {
    return this._(theKey).birthDate;
  };
  
  // Gets the full name.
  p.fullName = function() {
    var pData = this._(theKey);
    return pData.firstName + " " + pData.lastName;
  };
  
  // Get the string representation of this person.
  p.toString = function() {
    var pData = this._(theKey);
    return "I am a " + this.yearsOld() + " year old "
      + (pData.isMale ? "male" : "female") + " named " + pData.firstName + " "
      + pData.lastName + ".";
  };
  
  // Allow the person pseudo-class to exist outside of this closure.
  return Person;
})();

The code above defines a person pseudo-class in JavaScript. There are four members that are private to each instantiated person object: firstName, lastName, isMale, and birthDate. The first name and last name can be retrieved and set by using the public getters/setters firstName() and lastName(). In addition, fullName() is another accessor method. The person’s birth date can only be retrieved by using either birthDate() or valueOf(). In addition, you can find out how many years old the person is by using yearsOld(). Finally, you can print the person object out as a string to find all of the person’s information.

Now it is time for the tests:

var me = new Person("Chris", "West", true, "October 11, 1492");
var jade = new Person("Jade", "Williams", false, "April 28, 1984");
var harry = new Person("Harry", "Finkelstien", true, "June 4, 1987");

// Show my full name.
alert(me.fullName());

// Change Jade's last name and then show it.
alert(jade.lastName("Flintstone").fullName());

// Show how old harry is.
alert(harry.yearsOld());

// Show everything about the three people.
alert(me);
alert(jade);
alert(harry);

// An attempt to retrieve the private members from the outside.
alert("Could " + (me._({}) == undefined ? "not " : "")
  + "access the private members from the outside.");

I think the comments explain most of the test code pretty well. One thing to note is that, just because you pass in an empty object literal doesn’t mean that it is the same one that is needed to retrieve the private members.

In conclusion, as is demonstrated in my definition of the Person pseudo-class, prototypal functions can retrieve private members.

Deep Copy of Arrays

With jPaq and many other JavaScript libraries, it is very easy to do a shallow copy of an array. On the other hand, it isn’t as easy to do a deep copy of arrays. For this reason, I plan on replacing the Array.prototype.clone() function with the following:

Array.prototype.clone = function(doDeepCopy) {
    if(doDeepCopy) {
        var encountered = [{
            a : this,
            b : []
        }];

        var item,
            levels = [{a:this, b:encountered[0].b, i:0}],
            level = 0,
            i = 0,
            len = this.length;

        while(i < len) {
            item = levels[level].a[i];
            if(Object.prototype.toString.call(item) === "[object Array]") {
                for(var j = encountered.length - 1; j >= 0; j--) {
                    if(encountered[j].a === item) {
                        levels[level].b.push(encountered[j].b);
                        break;
                    }
                }
                if(j < 0) {
                    encountered.push(j = {
                        a : item,
                        b : []
                    });
                    levels[level].b.push(j.b);
                    levels[level].i = i + 1;
                    levels[++level] = {a:item, b:j.b, i:0};
                    i = -1;
                    len = item.length;
                }
            }
            else {
                levels[level].b.push(item);
            }

            if(++i == len && level > 0) {
                levels.pop();
                i = levels[--level].i;
                len = levels[level].a.length;
            }
        }

        return encountered[0].b;
    }
    else {
        return this.slice(0);
    }
};

The above code can be used to make deep copies of arrays. It is important to note, though, that only sub-arrays are deeply copied, not objects. The reason so much code is used is to even make it possible to make a deep copy of a recursive array structure. The following code exemplifies how to use this function:

// Create a recursive array to prove that the cloning function can handle it.
var arrOriginal = [1,2,3];
arrOriginal.push(arrOriginal);

// Make a shallow copy of the recursive array.
var arrShallowCopy = arrOriginal.clone();

// Prove that the shallow copy isn't the same as a deep copy by showing that
// arrShallowCopy contains arrOriginal.
alert("It is " + (arrShallowCopy[3] === arrOriginal)
    + " that arrShallowCopy contains arrOriginal.");

// Make a deep copy of the recursive array.
var arrDeepCopy = arrOriginal.clone(true);

// Prove that the deep copy really works by showing that the original array is
// not the fourth item in arrDeepCopy but that this new array is.
alert("It is "
    + (arrDeepCopy[3] !== arrOriginal && arrDeepCopy === arrDeepCopy[3])
    + " that arrDeepCopy contains itself and not arrOriginal.");

If you want to use this code right now, there is nothing stopping you. The code doesn’t require any library references and is fully cross-browser compatible. A JSBin example can be found here. It even works in JScript. The only thing that I ask is that you give credit where it is due. šŸ˜€

Power Set

As I was beefing up my past implementation of cartesianProductOf(), I decided to look into set theory a little bit more. After looking through some Wikipedia pages, I found another process that may be of interest to JavaScript programmers that have to deal with sets: a JavaScript implementation of finding a power set. Here is my code to do this:

function powerSetOf(arrFull) {
  var ret = [], fullLen = arrFull.length, last = fullLen - 1;
  function get(arrPrefix, start, lenToFind) {
    for(lenToFind--; start < fullLen; start++)
      if(!lenToFind)
        ret.push(arrPrefix.concat([arrFull[start]]));
      else if(start < last)
        get(arrPrefix.concat([arrFull[start]]), start + 1, lenToFind);
  }
  for(var lenToFind = fullLen; lenToFind > 0; lenToFind--)
    get([], 0, lenToFind);
  return ret.concat([[]]);
}

Although the above code works, I couldn’t help think that there was a simpler way of achieving the same goal. After fumbling around the idea in my head, I came up with the following, shorter solution:

function powerSetOf(arr) {
  var item, ret = [[]], max = arr.length - 1;
  function fn(arrPrefix, start) {
    for(; ret.push(item = arrPrefix.concat([arr[start]])) && start < max;)
      fn(item, ++start);
  }
  if(max + 1)
    fn([], 0);
  return ret;
}

Unlike the first solution, the above one does not return the results ordered by size. On the other hand, this solution is shorter, an works faster.

You may have noticed that neither solution removes duplicates if they exist within the array. If you want to remove all duplicates from an array, you can make a build of jPaq that has the Array.prototype.uniquify() function and then use it as follows:

// Your array of numbers or whatever.
var arr = [1,2,1,3,4,3,2];

// Get the power set for your array.
var arrPowerSet = powerSetOf(arr.uniquify());