arozwalak
7/2/2015 - 12:50 PM

Javascript: Inheritance

Javascript: Inheritance

Pseudoclassical

We can define a constructor and augment its prototype:

var Mammal = function (name) {
  this.name = name;
};
Mammal.prototype.get_name = function () {
  return this.name;
};
Mammal.prototype.says = function () {
  return this.saying || '';
};

// Now we can make an instance
var myMammal = new Mammal('Herb the Mammal');
var name = myMammal.get_name(); // 'Herb the Mammal'

// We can make another pseudoclass that inherits from Mammal by defining its constructor function 
// and replacing its prototype with an instance of Mammal
var Cat = function (name) {
  this.name = name;
  this.saying = 'meow';
};
// Replace Cat.prototype with a new instance of Mammal
Cat.prototype = new Mammal();

// Augment the new prototype with purr and get_name methods.
Cat.prototype.purr = function (n) {
  var i, s ='';
  for (i = 0; i < n; i += 1) {
    if (s) {
      s += '-';
    }
    s += 'r';
  }
  return s;
};
Cat.prototype.get_name = function () {
  return this.says() + ' ' + this.name + ' ' + this.says();
};

var myCat = new Cat('Henrietta');
var says = myCat.says(); // 'meow'
var purr = myCat.purr(5); // 'r-r-r-r-r'
var name = myCat.get_name(); // 'meow Henrietta meow'

We can hide some of the ugliness by using the method method and defining an inherits method

Function.method('inherits', function (Parent) {
  this.prototype = new Parent();
  return this;
});

// Our inherits and method methods return this, allowing us to program in a cascade style.
// We can now make our Cat with one statement
var Cat = function (name) {
  this.name = name;
  this.saying = 'meow';
}.
  inherits(Mammal).
  method('purr', function (n) {
    var i, s = '';
    for (i = 0; i < n; i += 1) {
      if (s) {
        s += '-';
      }
      s += 'r';
    }
    return s;
  }).
  method('get_name', function() {
    return this.says() + ' ' + this.name + ' ' + this.says();
  });

If you forget to include the new prefix when calling a constructor function, then this will not be bound to a new object. Sadly this will be bound to the global object, so instead of augmenting your new object, you will be clobbering global variables. There is no compile warning, and there is no runtime warning.

There is a convention that all constructor functions are named with an initial capital, and that nothing else is spelled with an initial capital.

Object Specifiers

var myObject = maker(f, l, m, c, s);

//instead:
var myObject = maker({
  first: f,
  last: l,
  middle: m,
  state: s,
  city: c
});

Prototypal

var myMammal = {
  name : 'Herb the Mammal',
  get_name : function () {
    return this.name;
  },
  says : function () {
    return this.saying || '';
  }
};

// Now we can make more instances with the Object.create method
var myCat = Object.create(myMammal);
myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.purr = function (n) {
  var i, s = '';
  for (i = 0; i < n; i += 1) {
    if (s) {
      s += '-';
    }
    s += 'r';
  }
  return s;
};
myCat.get_name = function() {
  return this.says() + ' ' + this.name + ' ' + this.says();
};

Functional

One weakness of the inheritance patterns we have seen so far is that we get no privacy. All properties of an object are visible. We get no private variables and no private methods.

We make a function that will produce objects. (name with lowercase letter ‘cause it will not require the use of the new prefix. The function contains four steps:

  1. Create a new object. — It can make an object literal, or — it can call a constructor function with the new prefix, or — it can use the Object.create method to make a new instance from an existing object, or — it can call any function that returns an object.
  2. It optionally defines private instance variables and methods. These are just ordinary vars of the function.
  3. It augments that new object with methods. Those methods will have privileged access to the parameters and the vars defined in the second step.
  4. It returns that new object.
var constructor = function (spec, my) {
  var that, other private instance variables;
  my = my || {};
  Add shared variables and functions to my
  that = a new object;
  Add privileged methods to that
  return that;
};

// Example:
var mammal = function (spec){
  var that = {};

  that.get_name = function () {
    return spec.name;
  };

  that.says = function () {
    return spec.saying || '';
  };
  return that;
};

var myMammal = mammal({name: 'Herb'});

var cat = function (spec) {
  spec.saying = spec.saying || 'meow';
  var that = mammal(spec);
  that.purr = function (n) {
    var i, s = '';
    for (i = 0; i < n; i += 1) {
      if (s) {
        s += '-';
      }
      s += 'r';
    }
    return s;
  };

  that.get_name = function () {
    return that.says() + ' ' + spec.name + ' ' + that.says();
  };
  return that;
};

var myCat = cat ({name: 'Henrietta'});

// superior method
Object.method('superior', function (){
  var that = this,
      method = that[name];
  return function (){
    return method.apply(that, arguments);
  };
});

var coolcat = function (spec) {
  var that = cat(spec),
      super_get_name = that.superior('get_name');
  that.get_name = function (n) {
    return 'like ' + super_get_name() + ' baby';
  };
  return that;
};

var myCoolCat = coolcat({name: 'Bix'});
var name = myCoolCat.get_name(); // 'like meow Bix meow baby'