Tuesday, August 17, 2010

Javascript Inheritance Done Right

I've seen a number of different ways of implementing Javascript inheritance. The real test to see if inheritance is working correctly is that the instanceof operator works correctly. So all the approaches that copy methods from the base to the subclass are out of the question because they do not correctly setup the prototype chain. Another consequence of inheritance by copying is that if you change the prototype after the object has been instantiated, the object does not magically inherit the added property.

This leads me to the most common approach which does correctly set up prototype chain but has a few problems:

function Animal(name) {
  this.name = name;
}

// This style of setting the prototype to an object
// works for classes that inherit from Object.
Animal.prototype = {
  sayMyName: function() {
    console.log(this.getWordsToSay() + " " + this.name);
  },
  getWordsToSay: function() {
    // Abstract
  }
}

function Dog(name) {
  // Call the parent's constructor
  Animal.call(this, name);
}

// Setup the prototype chain mmm... calling
// the Animal without the required params?
Dog.prototype = new Animal();

Dog.prototype.getWordsToSay = function(){
  return "Ruff Ruff";
}

var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Animal ???? That's not right
console.log("name" in Dog.prototype)// true, but undefined

Alright, what's going on?
  • Dog.prototype now has a property called "name" that is set to undefined.
    That wasn't intentional. I knew that call to Animal's constructor was funny. Though that won't cause a problem, because we add a "name" to the object in the constructor, it's not very elegant
  • dog (the instance) has a constructor property but it points to Animal,
    that's just wrong

How can we fix that? Here's a first try

// This is a constructor that is used to setup inheritance without
// invoking the base's constructor. It does nothing, so it doesn't
// create properties on the prototype like our previous example did
function surrogateCtor() {}

function extend(base, sub) {
  // Copy the prototype from the base to setup inheritance
  surrogateCtor.prototype = base.prototype;
  // Tricky huh?
  sub.prototype = new surrogateCtor();
  // Remember the constructor property was set wrong, let's fix it
  sub.prototype.constructor = sub;
}

// Let's try this
function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  sayMyName = function() {
    console.log(this.getWordsToSay() + " " + this.name);
  },
  getWordsToSay: function() {
    // Abstract
  }
}

function Dog(name) {
  // Call the parent's constructor
  Animal.call(this, name);
}

// Setup the prototype chain the right way
extend(Animal, Dog);

Dog.prototype.getWordsToSay = function(){
  return "Ruff Ruff";
}

var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Dog
console.log("name" in Dog.prototype)// false

Nice isn't it? Let's add some syntactic sugar to make it more user friendly.
  • Add a reference to the base class so we don't have to hard code it
  • Pass the object's prototype methods into the call


function surrogateCtor() {}

function extend(base, sub, methods) {
  surrogateCtor.prototype = base.prototype;
  sub.prototype = new surrogateCtor();
  sub.prototype.constructor = sub;
  // Add a reference to the parent's prototype
  sub.base = base.prototype;

  // Copy the methods passed in to the prototype
  for (var name in methods) {
    sub.prototype[name] = methods[name];
  }
  // so we can define the constructor inline
  return sub;
}

// Use the same animal from above
function Dog(name) {
  // Call the parent's constructor without hard coding the parent
  Dog.base.constructor.call(this, name);
}

extend(Animal, Dog, {
  getWordsToSay: function(){
    return "Ruff Ruff";
  }
});


One more step, I don't even like hard coding the name of the class in the methods in case I rename it, how can we fix that?

The solution is to wrap everything in a self executing function and create a reference to the constructor

Dog = (function(){
  // $this refers to the constructor
  var $this = function (name) {
    // Look, no hardcoded reference to this class's name
    $this.base.constructor.call(this, name);
  };

  extend(Animal, $this, {
    getWordsToSay: function(){
      return "Ruff Ruff";
    }
  });

  return $this;
})();

With this final approach, renaming the class or changing its parent requires changing a single place (for each change).

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);

Thursday, July 29, 2010

Canvas Rounded Corner Rectangles

I've recently started using the canvas tag and  needed to draw rounded corner rectangles. I did find a good post explaing how to do it but I didn't find a nice method so I decided to create one.  I took Futomi Hatano's code  and abstracted it into a method.

/**
 * Draws a rounded rectangle using the current state of the canvas. 
 * If you omit the last three params, it will draw a rectangle 
 * outline with a 5 pixel border radius 
 * @param {CanvasRenderingContext2D} ctx
 * @param {Number} x The top left x coordinate
 * @param {Number} y The top left y coordinate 
 * @param {Number} width The width of the rectangle 
 * @param {Number} height The height of the rectangle
 * @param {Number} radius The corner radius. Defaults to 5;
 * @param {Boolean} fill Whether to fill the rectangle. Defaults to false.
 * @param {Boolean} stroke Whether to stroke the rectangle. Defaults to true.
 */
function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
  if (typeof stroke == "undefined" ) {
    stroke = true;
  }
  if (typeof radius === "undefined") {
    radius = 5;
  }
  ctx.beginPath();
  ctx.moveTo(x + radius, y);
  ctx.lineTo(x + width - radius, y);
  ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
  ctx.lineTo(x + width, y + height - radius);
  ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
  ctx.lineTo(x + radius, y + height);
  ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
  ctx.lineTo(x, y + radius);
  ctx.quadraticCurveTo(x, y, x + radius, y);
  ctx.closePath();
  if (stroke) {
    ctx.stroke();
  }
  if (fill) {
    ctx.fill();
  }        
}

And it can be used like the following:

// My html contains a canvas with id "rounded-rect" 500X350
var ctx = document.getElementById("rounded-rect").getContext("2d");
// Draw using default border radius, 
// stroke it but no fill (function's default values)
roundRect(ctx, 5, 5, 50, 50);
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
roundRect(ctx, 100, 5, 100, 100, 20, true);
// Manipulate it again
ctx.strokeStyle = "#2d6";
ctx.fillStyle = "#abc";
roundRect(ctx, 100, 200, 200, 100, 50, true);

Which will produce the following output:




For Firefox and Chrome (and maybe other browsers), you can do the following for syntactic sugar

CanvasRenderingContext2D.prototype.roundRect = 

function(x, y, width, height, radius, fill, stroke) {
  if (typeof stroke == "undefined" ) {
    stroke = true;
  }
  if (typeof radius === "undefined") {
    radius = 5;
  }
  this.beginPath();
  this.moveTo(x + radius, y);
  this.lineTo(x + width - radius, y);
  this.quadraticCurveTo(x + width, y, x + width, y + radius);
  this.lineTo(x + width, y + height - radius);
  this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
  this.lineTo(x + radius, y + height);
  this.quadraticCurveTo(x, y + height, x, y + height - radius);
  this.lineTo(x, y + radius);
  this.quadraticCurveTo(x, y, x + radius, y);
  this.closePath();
  if (stroke) {
    this.stroke();
  }
  if (fill) {
    this.fill();
  }        
}

// Now you can just call
var ctx = document.getElementById("rounded-rect").getContext("2d");
ctx.roundRect(5, 5, 50, 50);

Here's a JsFiddle that you can edit freely