简体   繁体   中英

Javascript prototypal inheritance and OOP

I'm creating an application that allows a user to create widgets. There are several different types of widgets, and I have defined them using protypal inheritance. ie

//the base widget that all widgets inherit from
var Widget = function(){}        
Widget.prototype.someFunction = function(){}


//widget type A
var A = function(){}    
A.prototype = new Widget();


//widget type B
var B = function(){}    
B.prototype = new Widget();

I have discovered that it will be convenient to add a method on the base class that can create a new widget instance of the same type. ie

//the base widget
var Widget = function(){};        

Widget.prototype.clone = function(){
    switch(this.type){
         case 'A':
             return new A();
             break;
         case 'B':
             return new B();
             break;
         default:
             break;
    }
};

Which would allow me to get a new widget of the same type using the following code:

var widgetTypeA = new A();
var cloneOfWidgetTypeA = widgetTypeA.clone();

My concern is that the base widget now has to be explicitly aware of each of the types of widgets that inherit from it. Does this violate any principles of good OOP?

Widget.prototype.clone = function() {
  var constructor = window[this.type];
  return new constructor();
};

Assuming that all your subclasses are declared as globals of course.

But honestly I would out those sub classes in the Widget namespace, and access them through it rather than making everything global.

Widget.A = function(){};
Widget.A.prototype = new Widget();

Widget.prototype.clone = function() {
  var constructor = Widget[this.type];
  return new constructor();
};

Given that your constructors are globals, you could do something like:

var global = this;

Widget.prototype.clone = function() {
  if (global[this.type])
    return new global[this.type]();
};

Provided each instance has a type property whose value is the name of the constructor. Or you could fix the constructor property of constructor's prototype and do:

Widget.prototype.clone = function() {
    return new this.constructor();
};

function A() { };
A.prototype = new Widget();
A.prototype.constructor = A;

var a = new A();
var aa = a.clone();

However, that assumes that you don't have any parameters to pass. If you do have parameters to pass, then you likely have to know which type you are making and so can call the correct constructor anyway.

If ECMA5 is supported:

  • use Object.create(Object.getPrototypeOf(this));

If ECMA5 is not supported:

  • create an anonymous function
  • set the prototype of the anonymous function to the non-standard attribute this.__proto__

Example:

var Widget = function() { };

Widget.prototype.clone = function() {
  /*
   Non-ECMA5: 

   var newClone = function() {};
   newClone.prototype = this.__proto__;
   return new newClone(); 
  */

  // ECMA5
  return Object.create(Object.getPrototypeOf(this));
}


var A = function() { };
A.prototype = new Widget();
A.prototype.name = "I'm an A";

var B = function() { };
B.prototype = new Widget();
B.prototype.name = "I'm a B";

var x1 = new A();
var y1 = x1.clone();

console.log("y1 should be A: %s", y1.name);

var x2 = new B();
var y2 = x2.clone();

console.log("y2 should be B: %s", y2.name);

The information you need is already available in the constructor property. However, overwriting prototype will lose it as I recently explained here .

Using my own class implementation for ECMAScript version 3 or version 5 , your example would look like this:

var Widget = Class.extend({
    someFunction : function() {
        alert('someFunction executed');
    },

    clone : function() {
        return new this.constructor;
    }
});

var A = Widget.extend();

var B = Widget.extend({
    constructor : function(arg) {
        Widget.call(this); // call parent constructor
        this.arg = arg;
    },

    // override someFunction()
    someFunction : function() {
        alert('someFunction executed, arg is ' + this.arg)
    },

    // clone() needs to be overriden as well:
    // Widget's clone() doesn't know how to deal with constructor arguments
    clone : function() {
        return new this.constructor(this.arg);
    }
});

var a = new A;
var a2 = a.clone();
a2.someFunction();

var b = new B(42);
var b2 = b.clone();
b2.someFunction();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM