Showing posts with label constructor. Show all posts
Showing posts with label constructor. Show all posts

Thursday, August 5, 2010

Constructors without using "new"

When creating a constructor in Javascript, one has to worry about a problem: what if someone calls your constructor without using new?

function Point(x,y) {
  this.x = x;
  this.y = y
}

// This is good
var pt = new Point(20,30);

// This is not
var pt2 = Point(20,30);

You'll notice when you don't use new, pt2 is undefined after the call. Even worse, we've added two variables to the global scope, x, y. That is because if you call a function without specifying the context, the browser passes in the global object, which is "window";

The first attempt to fix this is the following

function Point(x,y) {
  if (this instanceof Point) {
    this.x = x;
    this.y = y
  } else {
    return new Point(x,y);
  }
}

// This is good
var pt = new Point(20,30);

// This is OK also
var pt2 = Point(20,30);

However, that seems like a lot of boiler plate code to add to every constructor. How can we abstract that? Here's what I came up with.

/**
 * Wraps the passed in constructor so it works with
 * or without the new keyword
 * @param {Function} realCtor The constructor function.
 *    Note that this is going to be wrapped
 *    and should not be used directly 
 */
function ctor(realCtor){
  // This is going to be the actual constructor
  return function wrapperCtor(){
    var obj; // object that will be created
    if (this instanceof wrapperCtor) {
      // Called with new
      obj = this;
    } else {
      // Called without new. Create an empty object of the
      // correct type without running that constructor
      surrogateCtor.prototype = wrapperCtor.prototype;
      obj = new surrogateCtor();
    }
    // Call the real constructor function
    realCtor.apply(obj, arguments);
    return obj;
  }
}

/** 
 * A function that does nothing, so we can create objects 
 * of a prototype without running unnecessary code. 
 * See its usage above
 */
function surrogateCtor() {}

// Create our point contructor
Point = ctor(function(x,y){
  this.x = x;
  this.y = y;
});

// This is good
var pt = new Point(20,30);
// This is OK also
var pt2 = Point(20,30);

// It's even ok with inheritance, though a 3D point shouldn't
// really inherit from a 2D point, it's just to make a point...
Point3D = ctor(function(x,y,z){
  Point.call(this, x, y);  
  this.z = z;
});

// Note that this is not the best way to setup inheritance.
// My next post will explain a better way
Point3D.prototype = new Point();

var pt3D = new Point3D(5,3,8);

// Outputs true
console.log(pt3D instanceof Point3D && pt3D instanceof Point);