1

I've been given a class -

Zoo.Controller = (function() {

  function Controller() {}

  Controller.prototype.params = {};

  Controller.prototype.set_params = function(params) {
    this.params = params;
    return this;
  };

  return Controller;

})();

and I want to inherit from that class using _.extend

Zoo.Controllers.WhaleController = _.extend({

  new: function () {
    // do something
  }

}, Zoo.Controller);

When I try to instantiate that class like so...

this.whale_controller = new Zoo.Controllers.WhaleController();

I get -

Uncaught TypeError: object is not a function

Is it possible to do what I'm trying? I've read multiple articles on inheritance in JS, but had assumed that the Underscore library had it solved for me.

Finnnn
  • 3,530
  • 6
  • 46
  • 69

6 Answers6

5

As Bergi pointed out; it isn't hard to inherit in JavaScript. You should know what a constructor function does and what prototype is used for. This answer may help with that, I tried to demonstrate prototype through simple and hopefully easy to understand examples. You can copy and paste the code in your browsers JS commandline (in the console) and change it to see if you understand how prototype behaves in JavaScript.

To inherit from ZooController you can:

Zoo.Controllers.WhaleController = function(args){
  Zoo.Controller.apply(this,arguments);//re use Zoo.Controller constructor
                                  //and initialize instance variables
  //instance specific members of Whale using an args object
  this.weitht=args.weight||4;
  this.wu=args.weightUnit||wu.metricTon;
  //Zoo.Controller.call(this,arg1,arg2); can be used too but I usually use
  // an args object so every function can pick out and mutate whatever they want
  // for example: var w = new WhaleController({weight:3,weightUnit:wu.metricTon});
  // now it looks more like pythons optional arguments: fn(spacing=15, object=o)
};
//set Zoo.controller.prototype to a shallow copy of WhaleController.prototype
//may have to polyfill the Object.create method if you want to support older browsers
Zoo.Controllers.WhaleController.prototype=Object.create(Zoo.Controller.prototype);
//repair constructor
Zoo.Controllers.WhaleController.prototype.constructor=Zoo.Controllers.WhaleController;
//extend Zoo.controller.prototype.set_params
Zoo.Controllers.WhaleController.prototype.set_params=function(){
  //re use parent set_params
  Zoo.Controller.prototype.set_params.apply(this,arguments);
  //and do something extra
  console.log("extra in set_params from WhaleController");
};
//WhaleController own function
Zoo.Controllers.WhaleController.prototype.whaleSpecific=function(){
  //funciton specific to WhaleController
};

Polyfill for Object.create here.

Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
3

I was wondering this myself, and this is what I came up with.

Define the parent

var parentObj = function(parentMemo) {
   console.log(parentMemo);
};

parentObj.prototype = {
    parentCall : function() {
        console.log('parentCall');
    }
}

Define the child

var childObj = function(childMemo, parentMemo) {
   parentObj.call(this, parentMemo);

   console.log(childMemo);
};

_.extend(childObj.prototype, parentObj.prototype, {
    childCall : function() {
       console.log('childCall');
    }
});

Construct a new a child to see the results

var newChild = new childObj("Constructing Child", "Constructing Parent");
newChild.childCall();
newChild.parentCall();

Console Results:

Constructing Child
Constructing Parent
childCall
parentCall 
Greg Tatum
  • 1,122
  • 10
  • 12
2

I've read multiple articles on inheritance in JS, but had assumed that the Underscore library had it solved for me.

No, Underscore does have no helper functions for prototypical inheritance. Read the docs on what extend does:

_.extend(destination, *sources): Copy all of the properties in the source objects over to the destination object, and return the destination object. It's in-order, so the last source will override properties of the same name in previous arguments.

Most interestingly, it does not return a function, but its first argument (which is a plain object).

So get back to the articles you've read, and choose a framework that does actually have an inherit function or implement the inheritance yourself - it's not hard.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    +1 for the `it's not hard` Instead of using tons of libraries that assist with it and may or may not do what you expect it to do it would be far easier to just do it yourself without a lot of methods that "help" you with it. If another person has to maintain your code he/she would have to spend extra time to get familiar with the framework that's used to do something that really isn't that hard. – HMR Dec 10 '13 at 01:40
1

John Resig has a good blog post on implementing Javascript inheritance that may be useful to you as it contains a solution for prototype inheritance, whereas Underscore extend is designed to extend simple Javascript objects.

Rob Willis
  • 4,706
  • 2
  • 28
  • 20
  • I personally think it's important to understand how prototype and objects created with a constructor function behave, what it's for and how to use it. Nothing against using a library to assist with this but it's so simple that I hardly see any use for it. It makes code hard to read as any project could use any library and inheritance behavior is unpredictable unless you reverse engineer what the library does. It would be far less work to learn how it's done in JS than to learn all the libraries that do it for you. – HMR Dec 10 '13 at 01:35
  • The post that I recommended is not a library, it contains an example of a custom prototype based inheritance implementation with a thorough explanation of how it works. – Rob Willis Dec 10 '13 at 09:12
0

You could reuse the extend() function used by backboneJs. Its a quite simple function that uses _.extend() too

http://backbonejs.org/docs/backbone.html#section-208

Then attach it to your Controller prototype so you could do something like:

var MyController = Controller.extend({ /* your attributes, methods */  });

hope this helps

MidnightLightning
  • 6,715
  • 5
  • 44
  • 68
pleasedontbelong
  • 19,542
  • 12
  • 53
  • 77
0

As others have explained, underscore's extend method creates (very) shallow copies of object instances and doesn't preserve prototype chains. However I recently came up against a small library — Compose.js — whose wider remit is to provide a more flexible API for JS's OO properties.

The second example in the readme seems to deal with your use case almost exactly — I believe in your situation it would be invoked as follows:

Zoo.Controllers.WhaleController = Compose( Zoo.Controller, { new: function () {
  // do something
} );
Barney
  • 16,181
  • 5
  • 62
  • 76