Friday, March 29, 2024

Some Useful JavaScript Object Creation Patterns

Following in the footsteps of Three JavaScript Anti-Patterns and How To Avoid Them, this article presents some useful patterns that are specifically aimed at aiding in the creation of Objects. Remember that a programming pattern represents a template for how to solve a problem that can be used in many different situations. The specific patterns that we’ll be covering here today are the enforcing the new keyword, the Factory pattern, as well as the method borrowing technique.

Enforcing the New Keyword When Creating Objects

Being able to create an object using a constructor function has both pros and cons, but overall, it’s one of the best ways to create your objects. The main downside is that you can still call the constructor without the new keyword, in which case, the this pointer will refer to the global window namespace – bad news to be sure. You can enforce the use of the new keyword using a simple test. Just insert it at the top of the object constructor method:

function createObjectFromPrototype() {
  var objectConstructor = arguments.callee.caller;
  // Create an object that extends the target prototype.
  var newClass = Object.create( objectConstructor.prototype );
  
  // Invoke the custom constructor on the new object,
  // passing in any arguments that were provided.
  objectConstructor.apply( newClass, objectConstructor.arguments );
  
  // Return the newly created object.
  return newClass;
}

function Person(name, sex) {
  //enforce the use of the new keyword
  if (!(this instanceof arguments.callee)) { return createObjectFromPrototype(); }
  
  this.populationCount++;  
  this.getName=function(){ return name };  
  this.getSex=function(){ return sex };  
  this.setSex=function(newSex){ sex = newSex; };  
  this.die=function(){ this.populationCount -- ; };
}
Person.prototype.populationCount=0;

var rob    = new Person('Rob','male');
var jeanie = Person('Jeanie','female');

alert(rob.getName());    //Rob
alert(jeanie.getName()); //Jeanie

The instanceof operator tests whether or not the this pointer refers to the Person. Recall that, without the new keyword, it would point to the global window object. With that in mind, an alternate syntax is:

if (this === window) {
  //new was not used!
}

From there, your script can create a new object instance using the Object.create() and Function.apply() methods. The createObjectFromPrototype() function doesn’t require any arguments because it can reference the Person constructor using the arguments.callee.caller property. Creating the object is a two-step process. First, the basic object is instantiated by calling Object.create() with the Person constructor’s prototype. Then, the constructor is called on the new class with the constructor’s original arguments. Finally, the new class is returned to the calling function, which returns immediately with the new object.

The Factory Pattern

Here’s how to avoid the “new” issue altogether. A factory defines an interface for creating an object from a collection of related subclasses. To create a class, you call the Factory’s build() function, passing in the desired subclass type. Within the build method, there’s yet another popular pattern called method borrowing. A method is copied if the subclass doesn’t have a function of the same name or it’s an inherited native method:

// parent constructor
function AutoFactory() {}

//override toString()
AutoFactory.prototype.toString = function () {
    return "I have " + this.numberOfHorses + " horses under the hood.";
};

AutoFactory.prototype.drive = function () {
    return "Vroom!";
};

// the static factory method
AutoFactory.build = function (constr) {
    // Throw an error if no constructor for the given automobile
    if (typeof AutoFactory[constr] !== "function") {
        throw {
            name:    "AutoFactoryError",
            message: "You cannot create " + constr + " automobiles in this factory"
        };
    }
    
    for (var fn in AutoFactory.prototype) {
      // Here, the method borrowing technique is used to 
      // selectively inherit from the AutoFactory
      if (  typeof AutoFactory[constr].prototype[fn] !== "function"
         || AutoFactory[constr].prototype[fn].toString().indexOf('[native code]') > -1 ) {
          AutoFactory[constr].prototype[fn] = AutoFactory.prototype[fn];
      }
    }
    // create a new automobile using the factory
    return new AutoFactory[constr]();
};

// define specific animal makers
AutoFactory.Prius = function () {
    this.numberOfHorses = 110;
};
AutoFactory.G35 = function () {
    this.numberOfHorses = 306;
};
AutoFactory.Porsche = function () {
    this.numberOfHorses = 400;
};
AutoFactory.Porsche.prototype.toString = function () {
  return "I have my own toString() method.";
};              

var prius = AutoFactory.build('Prius');
var g35 = AutoFactory.build('G35');
var porsche = AutoFactory.build('Porsche');
alert(prius); // "I have 110 horses under the hood."
alert(prius.drive()); // "Vroom!"
alert(g35); // "I have 306 horses under the hood."
alert(g35.drive()); // "Vroom!"
alert(porsche); // "I have my own toString() method."
alert(porsche.drive()); // "Vroom!"

Conclusion

The patterns that we reviewed here today are used a lot because they work. In fact, the Factory pattern is a staple of many Object-Oriented languages, including C++ and Java. We’ll be looking at more useful patterns in the upcoming months, but if you’re interested, the JavaScript Patterns book contains a lot of great info on the subject.

Rob Gravelle
Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured