Alexus
Alexus

Reputation: 1973

extending objects in Javascript

I'm kinda lost in getting the object extending to work. I have read dozens of sites related to this topic, but I'm still no wiser. It seems that everyone uses it's own approach to make this work, and so do I , I'm trying to find the best approach for extending/inheriting objects.

I am also aware that there are tons of frameworks/plugins out there to cover this functionality, but i'd just like to understand how it works in general. Not mentioning that most of these frameworks include lots of other stuff I may never use, hence I'm trying to make my own.

I was able to extend an object , everything seemed to be ok until I started adding methods to the target object. To understand the issue, please see the below example...

or just try this JSFiddle

The thing is, that after initializing the new instance of Rabbit object, I wasn't able to access Rabbit's method changeName. And I don't understand why it's happening, i.e why it doesn't recognize the method.

[*] Please see my updated code below (also the JFiddle), everything now seems to be working ok.

Can anoyne please advise, if this is a good approach or what am I missing?

var Class = (function(NewClass){
    if(NewClass.length != 0){
        var extend = function(target, source, args) {
            Object.getOwnPropertyNames(source).forEach(function(propName) {
                if(propName !== "Extend")
                {
                    Object.defineProperty(
                        target, propName,
                        Object.getOwnPropertyDescriptor(source, propName)
                    );
                }
                if (typeof source[propName] !== 'undefined'){
                    delete source[propName];
                }
            });

            return target;
        };
        var inherit = function(source, args){
            var baseClass = Object.getPrototypeOf(this); 
            baseClass.prototype = extend.call(this, baseClass, source, args);
        };
        if(NewClass.Extend){
            var Class = function(){ //// New Class Constructor ////
                if(typeof NewClass.Extend === 'function'){
                    NewClass.Extend.apply(this, arguments);
                    inherit.call(this, NewClass.Extend);
                    console.log(NewClass)
                    inherit.call(this, NewClass, arguments);
                    if(NewClass.Initialize){
                        NewClass.Initialize.call(this, arguments);
                    }
                }
            };
            Class.prototype.constructor = Class;
            return Class; 
        }
    }
});

var Animal =(function(args){//// constructor ////
    var self = this;
    self.name = typeof args !== 'undefined' ? args.name : null;
    self.bags = 0;
});

var Rabbit = new Class({
    Extend: Animal ,
    Initialize: function(){
        console.log(this.name)
    },
    changeName: function(a){

        console.log(this.name)
    }
});


var LittleRabbit = new Rabbit({name: "LittleRabbit", type: "None"});
console.log(LittleRabbit instanceof Rabbit)
console.log(LittleRabbit)
LittleRabbit.changeName("alex");

Upvotes: 1

Views: 168

Answers (3)

Brian Donovan
Brian Donovan

Reputation: 8390

I would suggest reading the MDN article detailing the JavaScript object model. It contains examples of "manually" subclassing:

function Employee() {
  this.name = "";
  this.dept = "general";
}

function Manager() {
  Employee.call(this);
  this.reports = [];
}
Manager.prototype = Object.create(Employee.prototype);

function WorkerBee() {
  Employee.call(this);
  this.projects = [];
}
WorkerBee.prototype = Object.create(Employee.prototype)

Translating your example to this style is simple:

function Animal(name) {
  this.name = name;
  this.bags = 0;
}

function Rabbit(name) {
  Animal.call(this, name);
  console.log(this.name);
}
Rabbit.prototype = Object.create(Animal.prototype);
Rabbit.prototype.changeName = function(name) {
  this.name = name;
};

Then you can easily run your example, modified a bit:

var LittleRabbit = new Rabbit("LittleRabbit");
console.log(LittleRabbit instanceof Rabbit)
console.log(LittleRabbit)
LittleRabbit.changeName("new name");

Once you understand this, I'd recommend not building your own class creation mechanism and just use ES6 classes:

class Animal {
  constructor(name) {
    this.name = name;
    this.bags = 0;
  }
}

class Rabbit extends Animal {
  constructor(name) {
    super(name);
    console.log(this.name);
  }

  changeName(name) {
    this.name = name;
  }
}

You can see this example in the Babel REPL. Some browsers/js runtimes natively support ES6 classes already, but you can use Babel to translate your code to ES5 for environments that don't yet.

As an aside, there is actually more that needs to be done to subclass completely correctly. A more complete example (that may not work in all environments) is this:

function Animal() {}
function Rabbit() {
  Animal.call(this);
}
Rabbit.prototype = Object.create(Animal.prototype);
Rabbit.prototype.constructor = Rabbit;
Rabbit.__proto__ = Animal;

Upvotes: 1

Grundy
Grundy

Reputation: 13381

your extend function work wrong, because Object.getPrototypeOf return prototype, so in more cases it object

var extend = function(source, args){
    var baseClass = Object.getPrototypeOf(this); 
    source.apply(this, args);

    //so here you just add property prototype to object, and this not same as set prototype to function.
    baseClass.prototype = Object.create(source.prototype);
};

So you can fix this like in snippet below:

function Class(args) {
  if (arguments.length != 0) {
    var C = function() {
      if (typeof args.Extend == 'function') {
        args.Extend.apply(this, arguments)
      }
      if (args.Initialize) {
        args.Initialize.call(this);
      }
    };
    if (typeof args.Extend == 'function') {
      C.prototype = Object.create(args.Extend.prototype);
    }

    Object.keys(args).filter(function(el) {
      return ['Extend', 'Initialize'].indexOf(el) == -1
    }).forEach(function(el) {
      C.prototype[el] = args[el];
    });

    return C;
  }
};

var Animal = (function(args) { //// constructor ////
  var self = this;
  self.name = typeof args !== 'undefined' ? args.name : null;
  self.bags = 0;
});

var Rabbit = Class({
  Extend: Animal,
  Initialize: function() {
    console.log(this.name);
  },
  changeName: function(a) {
    this.name = a;
  }
});


var LittleRabbit = new Rabbit({
  name: "LittleRabbit",
  type: "None"
});
console.log(LittleRabbit instanceof Rabbit);
console.log(LittleRabbit instanceof Animal);
console.log(LittleRabbit.name);
LittleRabbit.changeName('new little rabbit');
console.log(LittleRabbit.name);

Upvotes: 2

Jim Longo
Jim Longo

Reputation: 121

May ES6 class inheritance an option for you:

'use strict';
class Animal {
  constructor( name ) {
    this.name = name;
  }
  changeName( name ) {
    this.name = name;
  }
}

class Rabbit extends Animal {
  constructor() {
    super( 'rabbit' );
  }
}

let littleRabbit = new Rabbit();

console.log( littleRabbit.name ); //log default name

littleRabbit.changeName( 'littleRabbit' ); //executing an method of animal class

console.log( littleRabbit.name ); //log changed name

You don't need the "overhead" for making OOP inheritance for old good javascript because there are "translators" out there which translate your es6 code to es5 code. For Example babel: https://babeljs.io/

I think it is worth to give it a try...

Upvotes: 0

Related Questions