2

I want to create a model of a class, let's say

var car = function(){
  var color;
  this.setColor = function(clr){
    color = clr;
  }
}

Now I want some more classes, for example volvo(). But I also want all the stuff, car() has, to use it in volvo(), like this

var volvo = function(){
  ...probably some code here
  this.getColor = function(){
    return color;
  }
}

How do I do that?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Friedrich
  • 2,211
  • 20
  • 42
  • 1
    Do you mean pseudo-classical class-instance relationship? – gchiconi Dec 11 '13 at 23:41
  • Unless you have some sort of public-facing method or property that allows access to the colour in question, no. – Qantas 94 Heavy Dec 11 '13 at 23:42
  • You can use the Resig helper function but it may be a good idea to understand how constructor functions and prototype works in JavaScript. Maybe this answer can help with that: http://stackoverflow.com/a/16063711/1641941 – HMR Dec 12 '13 at 00:19

4 Answers4

2

What you are trying to achieve is called inheritance and since JavaScript is not class-based like Java, C# and other languages and it is based on prototypes, it is not that easy to achieve.

There are many ways of achieving it:

One way is to use frameworks such as Backbone.js, AngularJS or Ember.js. Then in your model class, basically you can use the Extend keyword and then get the inheritance out of the box.

Another way is that you include the following code, wrote by John Resig (creator of jQuery) into your application:

    /* Simple JavaScript Inheritance
     * By John Resig http://ejohn.org/
     * MIT Licensed.
     */
     // Inspired by base2 and Prototype
    (function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

  // The base Class implementation (does nothing)
  this.Class = function(){};

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

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;

    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[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, prop[name]) :
        prop[name];
    }

    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }

    // Populate our constructed prototype object
    Class.prototype = prototype;

    // Enforce the constructor to be what we expect
    Class.prototype.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;

    return Class;
  };
})();

After you load this code, you will be able to use it for your problem:

var Car = Class.Extend({
  setColor: function(clr){
    color = clr;
  }
});

var volvo = Car.Extend({
   getColor: function () {
      return color;
   }
});

Read about it in the JavaScript Inheritance by John Resig post. Good luck!

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ambodi
  • 6,116
  • 2
  • 32
  • 22
1

You can do it without any additional libraries - it's just slightly more long-winded.

jsFiddle

First set up a car class and give it some methods (setColor, setMake and setModel):

function car(make,model){
    console.log( 'car constructor' );
    this.setMake( make );
    this.setModel( model );
}
car.prototype.setColor = function(color){ this.color = color; };
car.prototype.setMake  = function(make ){ this.make  = make;  };
car.prototype.setModel = function(model){ this.model = model; };
car.prototype.toString = function(){ return 'This is a ' + this.color + ' ' + this.make + ' ' + this.model + ' car' };

Then you can inherit from the car:

function volvo( model ){
    console.log( 'volvo constructor' );
    car.call( this, 'volvo', model );
}
volvo.prototype = Object.create( car.prototype );
volvo.prototype.constructor = volvo;

Looking at the various sections then:

  • function volvo( model ){} defines a volvo class.
    • And within the constructor car.call( this, 'volvo', model ); is used to call the constructor of the parent class with a fixed make of volvo.
  • volvo.prototype = Object.create( car.prototype ); sets the volvo class to inherit from the car class.
  • volvo.prototype.constructor = volvo; is used to ensure that the volvo class uses the volvo constructor (without this line the previous statement would cause it to use the car constructor).

We can then test this:

var c = new car('Ford', 'Fiesta');
c.setColor('Red');
console.log( c.toString() );

var v = new volvo( 'S10' );
v.setColor( 'Grey' );
console.log( v.toString() );

The output is (including the log entries showing when the constructors were called):

car constructor
This is a Red Ford Fiesta car
volvo constructor
car constructor
This is a Grey volvo S10 car

And if you need a polyfill for Object.create then you can use:

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()
    }
  })()
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
MT0
  • 143,790
  • 11
  • 59
  • 117
  • Please don't inherit the prototype part by creating a new instance of Parent, use Object.create instead and polyfill it if you need support for older browsers. – HMR Dec 12 '13 at 00:37
  • If your inheritance goes 3 levels deep then you're in trouble if GrandChild calls `this.__super__.constructor.call(this,args);` when the Child constructor is called when `this` is GrandChild and `this.__super__` will refer to Child (not parent). You'll have a never ending loop of Child constructor calling itself. Instead use `Child.parent=Parent.prototype;` and in the constructor do: `Child.parent.constructor.call(this,args);` – HMR Dec 12 '13 at 00:41
0
function Car(color) {
    this.getColor = function() {
        return color;   
    };

    this.extend = function() {
        function F() {};
        F.prototype = this;
        return new F();
    };
}

car = new Car("black");
console.log(car.getColor());

vw = car.extend();
vw.slogan = function() { return "Das Auto"; };

console.log(vw.getColor());
console.log(vw.slogan());

gulf = vw.extend();
gulf.isHatchBack = function() { return true; };

console.log(gulf.getColor());
console.log(gulf.slogan());
console.log(gulf.isHatchBack());
robbmj
  • 16,085
  • 8
  • 38
  • 63
  • Interesting approach, how would I go about making 10 VW instances? – HMR Dec 12 '13 at 00:33
  • Since Car.extend returns an instance instead of a constructor you can't do `myOldBeetle=new VW(1972);` Where 1972 is the year it was built. So again, how would you create a bunch of VW __instances__? – HMR Dec 12 '13 at 00:46
  • People always try to make JS work like java and to me that is not the point of JS. embrace the it prototype system, don't think "is a" think "used to a, now is something else" it is a tribute to JavaScript that you can make it work something like a sudo classical language like Java, c++ or C#. In fact C# has copied it on several occasions. – robbmj Dec 12 '13 at 00:46
  • You don't have to throw the baby out with the bath water. Constructor initialization is a very useful thing. An object needs to be initialized at some point when an instance is created, this means setting up instance specific members. – HMR Dec 12 '13 at 00:54
  • well fair enough, in this instance, extend should be called copy. – robbmj Dec 12 '13 at 00:56
0

Probably you might find this also interesting: http://jsbin.com/IgOCAvI/1/edit

function Car() {
  this.carData = {};
}

Car.prototype = {

  set: function(ob) {
    var obType = Object.prototype.toString.call(ob);
    if( ob && obType.match('Obj') ){
      for (var key in ob) {
        if ( ob.hasOwnProperty(key) ){
          this.carData[key] = ob[key];
          // console.log("Stored: "+ key +':'+ ob[key] ); // TEST
        }
      }
    }else{
      // alert(ob +' ('+obType+')\n is not an Object!');
    } 
  }, 

  get: function(key) {
    if(typeof key == "string"){
      var arr = key.replace(/ /g, "").split(',');
      for(var i=0; i<arr.length; i++){ arr[i] = this.carData[arr[i]]; }
      // console.log( arr ); // TEST
      return arr;
    }else{ // Return the whole Object!
      // console.log( JSON.stringify(this.carData, null, 2) ); // TEST
      return this.carData;
    }
  }

};

Usage:

// Create new instance of Car 
var car1 = new Car();

// Populate with values
car1.set({manufacturer:"Volvo", model:"S40", color:"green", doors:5, damage:"front fender"});
// Get Array collection
console.log(  car1.get("color")  );                    // [Green]
console.log(  car1.get("color, doors, firstOwner")  ); // [Green, 5, undefined]
// Get whole Object
console.log(  car1.get()  );                           // [object Object]
// Get specific value from Object
console.log(  car1.get().color  );                     // "Green"
// Let's change a key value
car1.set({color:"red"});
// Test 
console.log(  car1.get()  );  


//  CAR2
var car2 = new Car();
// let's see if the car2 inherited some property of car1
console.log( car2.get('color') );  // [undefined] // nope.
car2.set({manufacturer:"Nissan", model:"Micra", color:"gray", doors:3, damage:"none"});
// ...
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313