Exploring JavaScript Inheritance

by bhayden
23. June 2012 22:22

Earlier I posted about becoming polyglot programmer.  Since then I’ve done a lot of reading regarding JavaScript (finishing Crockford’s excellent book) and one thing I needed to wrap my head around was how to “properly” do JavaScript inheritance.  Being a classical based language developer, it’s easy to try and take what JS provides and make it into something that looks and feel familiar to me.  Of course doing that is kind of missing the point on truly learning JS.

In the meantime we began a major project at work where we were going to be rewriting our JavaScript.  This seemed like the perfect opportunity to finally wrap my head around inheritance.  To give some context we were using YUI 2 with some jQuery sprinkled in.  We made the decision to move to jQuery completely and to bring in Kendo UI. 

The first scenario that popped up was our address control.  We have a basic address control, containing the typical address fields (street, city, state, zip and country).  We then have a shipping address control which adds to the the address control a name and phone field.  The base object looks something like this:

   1:  Address = {
   2:      street: "",
   3:      city: "",
   4:      state: "",
   5:      zip: "",
   6:      country: "",
   7:      fill: function(data) {
   8:          this.street = data.street;
   9:          this.city = data.city;
  10:          this.state = data.state;
  11:          this.zip = data.zip;
  12:          this.country = data.country;
  13:      }
  14:  }

We added a fill method to pass data in from AJAX calls and populate the object. Ideally the shipping address object would look something like this:

   1:  ShippingAddress = {
   2:      name: "",
   3:      phone: "",
   4:      /* fields from Address */
   5:      fill: function(data) {
   6:          this.name = data.name;
   7:          this.phone = data.phone;
   8:          base.fill(data);
   9:      }
  10:  }

The question did cross my mind as to why these need to be separate objects at all, but I’ll leave that discussion for another time (and place).  The neat thing about JavaScript inheritance is that it is prototypical, and that means that we can tell our shipping address object that its prototype is the address control (the neat thing about this is that we’re basically telling the shipping address object that it has all the behavior of the address object).  This is easy enough:

   1:  ShippingAddress.prototype = Address;

While this work, it lacks elegance to me, and it seems like a lot of people agree on that one.  The other thing we would like to do is be able to easily add functionality to our base Address object because that’s what we’re really after here.  We need all the functionality of the Address plus whatever we want to add to it.  In order to do this we add an extend method to the Address object.  This method does three things: creates a new a object, copies any properties that we want to add and then sets a parent reference so that the inherited objects can reference its base functionality.  Our final address object looks like this:

   1:  Address = {
   2:      street: "",
   3:      city: "",
   4:      state: "",
   5:      zip: "",
   6:      country: "",
   7:      fill: function(data) {
   8:          this.street = data.street;
   9:          this.city = data.city;
  10:          this.state = data.state;
  11:          this.zip = data.zip;
  12:          this.country = data.country;
  13:      },
  14:      extend: function(properties) {
  15:          var property, object;
  16:          object = Object.create(this);
  17:          for (property in properties) {
  18:              if (properties.hasOwnProperty(property)) {
  19:                  object[property] = properties[property];
  20:              }
  21:          }
  22:          object.parent = this;
  23:          return object;
  24:      }
  25:  };

And the final shipping address object changes to this:

   1:  demo.ShipAddress = demo.Address.extend({
   2:      name: "",
   3:      phone: "",
   4:      fill: function(data) {
   5:          this.parent.fill.call(this,data.Address);
   6:          this.fillMe(data.Instructions);
   7:      },
   8:      fillMe: function(data) {
   9:          this.name = data.name;
  10:          this.phone = data.phone;
  11:      }
  12:  });

The fillMe function isn’t strictly necessary.  I used this.parent.fill.call and set the context so that I could make this a kendo observable object and the binding would work correctly.  If you execute it  normally then the binding doesn’t work.  This occurs because it appears that Kendo elevates the base object properties onto the shipping address object.

The Kendo piece of the puzzle is fairly simple.  I created a simple wrapper function to create a shipping address and then return that as an observable:

   1:  demo.shipAddressViewModel = function() {
   2:      var _address = Object.create(demo.ShipAddress);
   3:      return kendo.observable(_address);
   4:  };

To put the it all together into a usable block of code that does something:

   1:  var viewModel = demo.shipAddressViewModel();
   2:  viewModel.fill(dummyData);
   3:  kendo.bind($("#addressForm"), viewModel);

Here we create a view model and then bind it to an address form on our page.  I created a JSFiddle that puts this all together, including the HTML.

Tags: ,

Comments are closed

RecentPosts