Function

Forcing A Constructor In JavaScript

Did you know that you can force a function to only act as a constructor? The following is an example of a Person pseudo-class which can be defined with or without the new keyword:

// Defines a person object.
function Person(firstName, lastName, age) { 
  // If called without parameters.
  if(!arguments.length) {
    // If this is a recursive call, allow the properties to now be defined.
    if(arguments.callee.caller === arguments.callee) {
      return this;
    }
    // If this is a non-recursive call, throw an error since the constructor
    // can't be called without parameters.
    else {
      throw new Error("No parameters were specified for the Person object.");
    }
  }
  
  // Allows make sure this function acts as a constructor.
  var me = !(this instanceof arguments.callee) ? new Person : this;
    
  // Define the properties.
  me.firstName = firstName;
  me.lastName = lastName;
  me.age = age;
  
  // Return a reference to the new Person instance.
  return me;
}

// Define the prototypal functions.
Person.prototype = {
  toString : function() {
    return this.firstName + " " + this.lastName + " is " + this.age
      + " years old.";
  }
};

One of the things that you may have noticed is the fact that I am throwing an error if the constructor is called without any arguments. If you don’t want that to happen, you can simply remove that else statement. Everything is probably pretty self explanatory.

The following are two examples which prove the above code actually works:

// Create an instance of a person without the "new" keyword.
var p = Person("Chris", "West", 23);
alert(p.firstName);  // display "Chris"
alert(p instanceof Person);  // display true
alert(p);  // use toString() function

// Create an instance of a person with the "new" keyword.
var p = new Person("Tamara", "Thomas", 21);
alert(p.age);  // display 21
alert(p instanceof Person);  // display true
alert(p);  // use toString() function.

Function Overloading in JavaScript

Method overloading is one of the nice things that exists in real programming languages such as Java. Even though JavaScript starts with “Java”, there are very few similarities between the two languages. Still, due to the power of the scripting language, there is a way to overload functions. The most commonly used method is to simply write some if statements to examine the arguments. Still, I thought it would be nice to be able to overload functions in a more natural way. Therefore, I wrote the following prototype function for functions which will be included in the next version of jPaq:

// Function.prototype.overload - By Chris West - MIT Licensed
Function.prototype.overload = function(fnToAdd, objFilters) {
  var fnOriginal = this;
  return function() {
    var fnToUse = fnToAdd, args = arguments, len = args.length;
    if(objFilters) {
      for(var key in objFilters) {
        if((key == "all" && !objFilters[key].call(this, args))
           || (key == "length" && len != objFilters[key])
           || (/^\d+$/.test(key) && !objFilters[key](args[key]))) {
          fnToUse = fnOriginal;
          break;
        }
      }
    }
    else if(fnToAdd.length !== len) {
      fnToUse = fnOriginal;
    }
    return fnToUse.apply(this, args);
  };
};

First Parameter (fnToAdd)

The first parameter that you pass in must be the function that you want to overload the current function.

Second Parameter (objFilters)

The second parameter is optional. If it is not specified, the only way that fnToAdd will be used is if the amount of arguments specified in the header of that function matches the number of arguments passed in. If the second parameter is specified, it must be an object literal which will be used to determine whether or not to use this function instead of the original. To filter based on a specific parameter, you should make the key be the index of the parameter and the value should be the function that will check that parameter. This filter function will only receive the specified parameter as its only argument. If you want to filter by the amount of parameters passed, you can define the length of the object literal.  If you want use an all-purpose filter function, you can define the all property of the object literal as a function which will receive all of the parameters just as the function would.  In addition, this all-purpose filter function will also receive the this object of the function.  Just as in the case of the other filter functions, if the return value evaluates to false, the original function will be used, otherwise the new function will be used.

Test Code

Now, instead of trying to clarify the above description with more words, let’s let the code do the talking. Assuming that the definition for Function.prototype.overload() appears before this code, the following will test and prove that function overloading can be quite easy:

// Returns a boolean indicating whether or not the argument is an array.
function isArray(arg) {
  return Object.prototype.toString.call(arg) == "[object Array]";
}

// Returns a boolean indicating whether or not the argument is a number.
function isNumber(arg) {
  return Object.prototype.toString.call(arg) == "[object Number]";
}

// The default callAny() function.
function callAny() {
  alert("Default callAny() function was called.");
};

// The callAny() function that will be used if the first parameter is an array.
function callArray(arr) {
  alert("callArray() called with [" + arr.join(", ") + "]");
}

// The callAny() function that will be used if two numbers are passed.
function callNum(num1, num2) {
  alert("callNum() called with " + num1 + " and " + num2);
}

// The callAny function that will be used if no arguments are passed.
function callNone() {
  alert("callNone() called without arguments as expected.");
}

// Easily overload the callAny() function.
callAny = callAny
  .overload(callNone)
  .overload(callArray, {0 : isArray})
  .overload(callNum, {0 : isNumber, length : 2});

/********** Example calls the callAny() **********/
// Calls the default function.
callAny();

// Calls the callNum() function.
callAny(1, 2);

// Calls the default function.
callAny(3);

// Calls the callArray() function.
callAny(["Hello", "World!!!"]);

The first thing to note is that the isNumber() and isArray() functions are defined to filter the arguments. Both callNone(), callArray() and callNum() will overload callAny(). If callAny() is called without any parameters, it acts as if callNone() was invoked. If callAny() is called with the first parameter being an array, it acts as if callArray() was called. If callAny() is called with the first parameter being a number and is called with exactly two parameters, it acts as if callNum() was called. In all other cases the default version of callAny() is called.

Future

I personally think that this may become useful for other JavaScript programmers. Therefore, I plan on incorporating this function into jPaq. Unfortunately, I don’t have an exact date for the next release, therefore, I am fine with you using this code as long as you give me credit for my code. 8)

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.