Software by Steven

Sometimes a square peg is a round hole

Re-learning JavaScript OOP well

It’s been a while since I’ve done some more serious JavaScript development, and even then it was typically very procedural. With adding version-dependent features for Popcorn’s SSA/ASS subtitle parser, I figured this would be a good opportunity to learn. I remember from the past that JavaScript uses Prototypical Inheritance, something I have no experience with in other languages. Luckily, I didn’t have to go far to find some helpful resources.

In fact, MDN has an article with everything I needed to know, with a lecture by Douglas Crockford filling in the gaps.

Inheritance and working off the prototype are what I’ve struggled with most, so I’m including some code snippets below. Most are inspired or outright copied and adapted from the MDN article. It’s also on pastebin.

// Define a constructor for an 'SSAParser' object
function SSAParser(text) {
  this.text = text;
  this.lines = [];
}

// Define a function for our parser object. So that memory is only allocated for it once across many objects, we put it on the prototype.
// Inside the prototype, 'this' refers to the instance object
SSAParser.prototype = {
  parse: function() {
    return "SSA parser: " + this.text;
  },

  getName: function() {
    return "SSA parser";
  }
};

// Instantiate with 'new' so that the properties are put on a new object (called 'this' in the constructor)
var parser = new SSAParser("1");
var parsed = parser.parse();

// Not using 'new' will cause it to be like a normal function call.
// The result will have 'this' point to the function itself, causing unpleasantness
var parser = SSAParser("1");

try {
  var parsed = parser.parse();  // SSAParser doesn't return a value, so 'var parser' is undefined, causing an error
} catch (e) { alert("Please use 'new'"); }

// Being clever, we can avoid using 'new' by always calling the function directly, and explicitly specifying 'this' to be our parser object. This results in far more typing.
var parser = {};
SSAParser.call( parser, text );
var parsed = SSAParser.prototype.parse.call(parser);

// Everything on the prototype is publicly accessible
// Using closures, we can adapt the above method to have hidden helper functions that aren't publicly accessible
SSAParser.prototype.useHelper = (function (){
  // This function is defined once, but is not directly on the prototype.
  // It is not publicly visible, but as it's defined in the same closure as a function on the prototype, it is accessible
  // 'this' points to the 'getHelperText' function by default, unless we override it
  function getHelperText() {
    return "From hidden helper: " + this.text;
  }

  // This function ends up on the prototype, we can access 'this' properly from within it
  return function() {
    var text = "From helper: " + this.text;

    // We can call getHelperText and reference our instance members within.
    // It becomes a private member function
    text += getHelperText.call(this);
    return this.text;
  }
})();

// Inheritance, ASSParser extends SSAParser
function ASSParser(text) {
  // Explicitly call parent constructor
  SSAParser.call( this, text );
}

// We set the prototype to be our base class and update the constructor
ASSParser.prototype = new SSAParser();
ASSParser.prototype.constructor = SSAParser;

// Override parent definition of getName
// Unless we store the old definition of 'getName' we can no longer call the parent version
ASSParser.prototype.getName = function() {
    return "ASS parser";
}

// Define new function, which references parent
ASSParser.prototype.getPi = function() {
    return Math.PI;
}

var parser = new ASSParser("sample");
alert(parser.getName());
alert(parser.getPi());
About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: