5

Just for the sake of curiosity, I was playing with prototypal inheritance and OOP inheritance in Javascript. Most results involve emulating 'Class' and 'extends' concepts with functions, while others use the prototype and constructors.

I wrote this code:

function Warrior(weaponName) {
    var weapon = weaponName;
    this.getWeapon = function() {
        return weapon;
    };
    this.setWeapon = function(value) {
        weapon = value;
    };
    this.displayInfo = function() {
        return {
            "weapon": this.getWeapon(),
        };
    };
}

function Archer() {
    var accuracy = "86%";
    this.parent = Archer.prototype; // Inheritance workaround
    this.getAccuracy = function() {
        return accuracy;
    };
    this.setAccuracy = function(value) {
        accuracy = value;
    };
    this.displayInfo = function() {
        var form = this.parent.displayInfo();
        form.accuracy = this.getAccuracy();
        return form;
    };
}
Archer.prototype = new Warrior("bow");
var w = new Warrior("sword");
var a = new Archer();
console.log(w.displayInfo());
console.log(a.displayInfo());

I made this so when displaying the information from the Warrior class, it shows the object as

{ weapon: "sword" }

And when the information from Archer is shown, the object is:

{ weapon: "sword", accuracy: "86%" }

The "subclass" is taking information from the "superclass" and adding to it. Calling "getWeapon()" or "setWeapon" from Archer also works. The chain goes on without problems, even when I add a third class "Kyudoka" that extends "Archer" and has it's own properties as well.

But comparing to the more complex code I found while researching, I feel this could be a naive implementation (the "Inheritance workaround" line) and I'm missing something (considering that JS has a lot of subtlety).

This is a theorical question, I'm not using this code in any system.

thefourtheye
  • 233,700
  • 52
  • 457
  • 497
adrield
  • 629
  • 6
  • 19
  • You can use `Object.getPrototypeOf(this)` instead of hardcoding the `Archer.prototype` as a property. – Lye Fish Aug 27 '15 at 19:19
  • Every instance of the Warrior creates 3 function. So for example if you have 3 archers, then it will result in 300 function. By prototypal inheritance you create only 3 functions, no matter how many instances you have. There can be some optimization on this, I don't know. – inf3rno Aug 27 '15 at 19:21
  • This article helped me: [JavaScript object creation](http://www.adobe.com/devnet/archive/html5/articles/javascript-object-creation.html) – WhiteHat Aug 27 '15 at 19:23
  • @WhiteHat - The problem with articles like that, is they over-complicate the essence of what they're trying to show you. He shows too many concepts for a perfunctory task. – Data Aug 27 '15 at 19:34
  • @LyeFish - thanks! Less hardcoding is always good! – adrield Aug 27 '15 at 20:43
  • @inf3rno - think I could reduce the number of functions if I used
    Object.defineProperties()
    , I'm going to give it a try.
    – adrield Aug 27 '15 at 20:43
  • @WhiteHat - thanks for the reference – adrield Aug 27 '15 at 20:43
  • `Archer.prototype = new Warrior("bow");` is wrong, see https://stackoverflow.com/questions/12592913/what-is-the-reason-to-use-the-new-keyword-here and http://stackoverflow.com/a/17393153/1048572/Benefits-of-using-Object.create-for-inheritance – Bergi Aug 27 '15 at 21:25
  • You must not use a `this.parent` property like that. It won't work in subclasses of `Archer`, like [in this example](http://stackoverflow.com/a/24700014/1048572). Use a [correct](http://stackoverflow.com/a/25060720/1048572) [pattern](http://stackoverflow.com/a/16105890/1048572) – Bergi Aug 27 '15 at 21:35
  • @adrield Object.defineProperty is buggy. Good luck with it! Btw. you should use Object.create or at least create the descendant prototype with a different constructor. – inf3rno Aug 28 '15 at 05:49
  • @Bergi - a curious behaviour, "this.parent" actually worked with a third class in the chain without breaking the stack, but trying to put "parent" variable in the prototype blew up the whole thing on Chrome! Thanks for the links! :) Edit: typo – adrield Aug 28 '15 at 14:48

2 Answers2

2

Put the functions on the prototype...

function Warrior(weaponName) {
    this.weapon = weaponName;
}

Warrior.prototype = {

    getWeapon : function() {
        return this.weapon;
    },

    setWeapon : function(value) {
        this.weapon = value;
    },

    displayInfo : function() {
        return { "weapon" : this.getWeapon() };
    }
};

//----------------------------------

function Archer(weaponName) {
    Warrior.call(this, weaponName);
    this.accuracy = "86%";
}

Archer.prototype = Object.create(Warrior.prototype);
Archer.prototype.constructor = Archer;

Archer.prototype.getAccuracy = function() {
    return this.accuracy;
};

Archer.prototype.setAccuracy = function(value) {
    this.accuracy = value;
};

Archer.prototype.displayInfo = function() { 
    return "weapon: " + this.getWeapon() + ", accuracy: " + this.getAccuracy();
};


//----------------------------------

var w = new Warrior("sword");
var a = new Archer("axe");
console.log(w.displayInfo()); // Object {weapon: "sword"}
console.log(a.displayInfo()); // weapon: axe, accuracy: 86%

Edit: fixed recursion

Data
  • 1,337
  • 11
  • 17
  • 2
    Your implementation of `displayInfo` on the `Archer` creates infinite recursion because it doesn't call `Weapon.displayInfo`. – Mike Cluck Aug 27 '15 at 19:33
  • @Mike C - well spotted, I just copied the OP code without going into the minuatia. – Data Aug 27 '15 at 19:36
  • @Data - thanks for the insight on prototypal! I understand that I could just override the Archers' displayInfo() with the data I need, but I was trying to come up with a way to actually use the 'super' method. But I will try a bit with the Object.create and the constructor, as you described and see what happens! – adrield Aug 27 '15 at 20:36
  • @adrield - I wouldn't bother with things like `super`, it doesn't fit with JS prototypal structure. You create objects with own properties and link the prototype to another prototype . JS can only look down the chain and not up, so trying to hack a super reference defeats the goal of prototypal inheritance/delegation. – Data Aug 27 '15 at 20:40
2

There are mainly 3 kinds of inheritance in javascript, according to the book Javascript the Good Parts: Pseudoclassical, Prototypal and Functional.

The one you just posted would fit under the Pseudoclassical inheritance, where you emulate a Class behaviour using constructor functions.

I find more useful and flexible the Functional pattern, which allows you to protect your variables (make them private).

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

Prototypal is basically having your objects inherit directly from other useful object, which would be something like having them (the useful objects) as your new object constructor prototype.

Object.beget = function (o) {
  var F = function () {};
  F.prototype = o;
  return new F();
};

var a = {}
//Add shared variables to a
var b = Object.beget(a);
//Add new methods to b

That are many considerations to each of the patterns, for instance Crockford says in his book "The functional pattern has a great deal of flexibility. It requires less effort than the pseudoclassical pattern, and gives us better encapsulation and information hiding and access to super methods.", but I've also seen articles arguing the other way around, such as this http://bolinfest.com/javascript/inheritance.php

EDIT ------

In case you might want to know different aproaches to reaching super methods, in the Functional pattern you can do the following:

Function.prototype.method = function (name, func) {
  this.prototype[name] = func;
  return this;
};

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

var archer = function (spec, accuracy) {
  var that = warrior(spec),
  super_displayInfo = that.superior('displayInfo');
  that.getAccuracy = function() {
    return accuracy;
  };
  that.setAccuracy = function(value) {
    accuracy = value;
  };
  that.displayInfo = function (n) {
    var form = super_displayInfo()
    form.accuracy = that.getAccuracy();
    return form;
  };
  return that;
};
Roger
  • 1,053
  • 1
  • 8
  • 14
  • thanks! indeed, "Pseudoclassical" sound more like I was going for. I was putting some thought on private variables in this setup. By default, I would only use "var varable_name" inside the "class" and then expose it as needed with Object,defineProperty / Object,defineProperties getters and setters. I still have to fiddle a bit more with the idea, I guess it would not cover the "protected" variable cases. – adrield Aug 27 '15 at 20:57
  • No problem! I added an different workaround to reaching superior methods if you are interested =) – Roger Aug 27 '15 at 21:19