24

I'm looking for a simple way of creating two classes, one inheriting from the other, and the child redefining one of the parent's methods, and inside the new method, calling the parent's.

For example, having a class Animal and Dog, where the Animal class defines a method makeSound() which establishes how to output a sound, which Dog then overrides in its own makeSound() method to make a "woof" sound, but while also calling Animal's makeSound() to output that woof.

I looked at John Resig's model here, but it uses the native arguments.callee property which is apparently depreciated in ECMA script 5. Does that mean I shouldn't use John Resig's code?

What would one neat, simple way of writing my animal/dog code using Javascript's prototype inheritance model?

Matthew Strawbridge
  • 19,940
  • 10
  • 72
  • 93
Jodes
  • 14,118
  • 26
  • 97
  • 156
  • 1
    A broader scope response: Check out these great video lectures from JSON inventor and Javascript god (Yeah I know all gods also have faults but god he is nethertheless, think Greek gods not "The One") Douglas Crockford: playlists http://www.youtube.com/playlist?list=PL5586336C26BDB324 and http://www.youtube.com/playlist?list=PL7664379246A246CB – Mörre Feb 24 '13 at 10:31
  • The following answer contains code for creating instances of a certain type, inheriting from them and overriding/extending parent functions, maybe it'll be helpful: http://stackoverflow.com/a/16063711/1641941 – HMR Dec 07 '13 at 08:56

4 Answers4

27

Does that mean I shouldn't use John Resig's code?

Correct, not when you are using ES5 in strict mode. However, it can be easily adapted:

/* Simple JavaScript Inheritance for ES 5.1
 * based on http://ejohn.org/blog/simple-javascript-inheritance/
 *  (inspired by base2 and Prototype)
 * MIT Licensed.
 */
(function(global) {
  "use strict";
  var fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

  // The base Class implementation (does nothing)
  function BaseClass(){}

  // Create a new Class that inherits from this class
  BaseClass.extend = function(props) {
    var _super = this.prototype;

    // Set up the prototype to inherit from the base class
    // (but without running the init constructor)
    var proto = Object.create(_super);

    // Copy the properties over onto the new prototype
    for (var name in props) {
      // Check if we're overwriting an existing function
      proto[name] = typeof props[name] === "function" && 
        typeof _super[name] == "function" && fnTest.test(props[name])
        ? (function(name, fn){
            return function() {
              var tmp = this._super;

              // Add a new ._super() method that is the same method
              // but on the super-class
              this._super = _super[name];

              // The method only need to be bound temporarily, so we
              // remove it when we're done executing
              var ret = fn.apply(this, arguments);        
              this._super = tmp;

              return ret;
            };
          })(name, props[name])
        : props[name];
    }

    // The new constructor
    var newClass = typeof proto.init === "function"
      ? proto.hasOwnProperty("init")
        ? proto.init // All construction is actually done in the init method
        : function SubClass(){ _super.init.apply(this, arguments); }
      : function EmptyClass(){};

    // Populate our constructed prototype object
    newClass.prototype = proto;

    // Enforce the constructor to be what we expect
    proto.constructor = newClass;

    // And make this class extendable
    newClass.extend = BaseClass.extend;

    return newClass;
  };

  // export
  global.Class = BaseClass;
})(this);
SleepyCal
  • 5,739
  • 5
  • 33
  • 47
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • This works realy well, and to make sure it still works in legacy IE, you could include this polyfill `if (!Object.create) { Object.create = (function(){ function F(){} return function(o){ if (arguments.length != 1) { throw new Error('Object.create implementation only accepts one parameter.'); } F.prototype = o; return new F() } })(); }` – shane Jul 07 '13 at 17:04
  • This causes instances generated using `Class.extend()` to have a displayed type of `Class.extend.init` and subclasses to have a displayed type of `proto.(anonymous function)`. Resig's original snippet gives all instances a displayed type of `Class`. That said, running `* instanceof Class` returns `true` for this rewrite, but it's still distracting and ugly in the browser console. – Adrian Jul 22 '14 at 04:56
  • 1
    @Adrian: If you don't like your debugger's types, use [named functions](http://blog.niftysnippets.org/2010/03/anonymouses-anonymous.html) (for the `init` property). I've also named the default constructors now. – Bergi Jul 22 '14 at 11:21
  • Aha. Silly me. I should not try to read JS code while tired. Thanks for the update. :) – Adrian Jul 22 '14 at 18:50
  • What i understand is that init is like your constructor is there a destructor method which gets called before the object is getting destroyed? – paramupk Jul 06 '15 at 08:56
  • @paramupk: [There are no destructors in JavaScript](http://stackoverflow.com/q/10112670/1048572) – Bergi Jul 06 '15 at 11:51
7

Prototype chain with Object.create() + assign constructor

function Shape () {
    this.x = 0;
    this.y = 0;
}

Shape.prototype.move = function (x, y) {
    this.x += x;
    this.y += y;
};

function Rectangle () {
    Shape.apply(this, arguments); // super constructor w/ Rectangle configs if any
}

Rectangle.prototype = Object.create(Shape.prototype); // inherit Shape functionality
// works like Rectangle.prototype = new Shape() but WITHOUT invoking the constructor

Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

rect instanceof Rectangle && rect instanceof Shape // returns true

from Object.create documentation

information about the new keyword

Community
  • 1
  • 1
neaumusic
  • 10,027
  • 9
  • 55
  • 83
3

This is something I came up with for inheritance using chaining as well as allowing _super to work.

/**
 * JavaScript simple inheritance
 * by Alejandro Gonzalez Sole (base on John Resig's simple inheritance script)
 * MIT Licensed.
 **/
(function (){
    var initializing = false,
      fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.* /;

    function Class(){};

    function inheritClass(superClass){
      var self = this;
      function Class(){
        if (!initializing && typeof this._constructor === 'function')
          this._constructor.apply(this, arguments);
      }

      Class.prototype = superClass.prototype;
      Class.prototype._constructor = superClass;
      Class.prototype.constructor = Class;
      Class.extend = extendClass;
      //currenlty if you inhert multiple classes it breaks
      Class.inherit = inheritClass;
      return Class;
    };

    function extendClass(prop) {
      var self = this;
      var _super = self.prototype;

      function Class(){
        if (!initializing && typeof this._constructor === 'function')
          this._constructor.apply(this, arguments);
      }

      initializing = true;
      var prototype = new self();
      initializing = false;

      for (var name in prop) {
        prototype[name] = typeof prop[name] == "function" &&
          typeof _super[name] == "function" && fnTest.test(prop[name]) ?
          (function(name, fn){
            return function() {
              var tmp = this._super;
              this._super = _super[name];
              var ret = fn.apply(this, arguments);
              this._super = tmp;
              return ret;
            };
          })(name, prop[name]) : prop[name];
      }

      Class.prototype = prototype;
      Class.prototype.constructor = Class;
      Class.extend = extendClass;
      Class.inherit = inheritClass;

      return Class;
    };

    Class.extend = extendClass;
    Class.inherit = inheritClass;

})();


//EXAMPLE

function Person(){
  this.name = "No name";
  console.log("PERSON CLASS CONSTRUCTOR")
}
Person.prototype.myMethod = function (t){
  console.log("MY PERSON", t, this.name);
  return -1;
}

var TestPerson = Class.inherit(Person).extend({
    _constructor: function(){
      this._super();
      this.name = "JOhn";
      console.log("TEST PERSON CONSTRUCTOR");
    },
    myMethod: function (t){
      console.log("TEST PERSON", t, this.name);
      return this._super(t)
    }
});


var test = new TestPerson();

console.log(test.myMethod("BA"));

I've been testing it on my pixi wrapper https://github.com/guatedude2/pixijs-cli so far it's been working very well for me.

The only issue i've encountered with this approach is that you can only inherit once. If you run inherit again it will override the previous inheritance.

mrk
  • 4,999
  • 3
  • 27
  • 42
guatedude2
  • 218
  • 3
  • 5
  • I did write an example and is just this is just a different approach. I updated the latest version i'm using. It's similar to the one that Resig wrote but it adds inheritance to prototype classes and works on ES6. – guatedude2 Apr 08 '15 at 03:37
2

I prefer the way TypeScript generates a form of inheritance (Select Simple Inheritance from the dropdown). That one doesn't use arguments.callee, but an __extends prototype.

var __extends = this.__extends || function (d, b) {
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var Animal = (function () {
    function Animal(name) {
        this.name = name;
    }
    Animal.prototype.move = function (meters) {
        alert(this.name + " moved " + meters + "m.");
    };
    return Animal;
})();
var Snake = (function (_super) {
    __extends(Snake, _super);
    function Snake(name) {
        _super.call(this, name);
    }
    Snake.prototype.move = function () {
        alert("Slithering...");
        _super.prototype.move.call(this, 5);
    };
    return Snake;
})(Animal);
var Horse = (function (_super) {
    __extends(Horse, _super);
    function Horse(name) {
        _super.call(this, name);
    }
    Horse.prototype.move = function () {
        alert("Galloping...");
        _super.prototype.move.call(this, 45);
    };
    return Horse;
})(Animal);
var sam = new Snake("Sammy the Python");
var tom = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
MarcoK
  • 6,090
  • 2
  • 28
  • 40