Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
228 views
in Technique[技术] by (71.8m points)

javascript - JS OO Pattern with Prototype and Base Class

is this a good pattern for OO JS? What I am looking for is an easy way to solve inheritance in JavaScript.

function MySuperClass(arg)
{
    this.arg1 = arg;
}
function MyBaseClass(arg)
{
    this.base = MySuperClass;
    this.base(arg);
    this.arg2 = arg;
}
MyBaseClass.prototype = new MySuperClass();
function MySpecificClass(arg)
{ 
    this.base = MyBaseClass;
    this.base(arg);
    this.arg3 = arg;
}
//ensures inheritance of all properties
MySpecificClass.prototype = new MyBaseClass();

var myFirstInstance = new MySpecificClass("test");
var mySecondInstance = new MySpecificClass("test2");
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Note: See the end for an ES2015 update.

ES5 and earlier

There are a couple of problems there.

  1. Your MySuperClass function expects an argument, but you can't give it one when you're calling it to create the MyBaseClass.prototype.

  2. The base property you're setting on the instance won't work correctly for the code in MyBaseClass, because MyBaseClass expects that to be MySuperClass, but it isn't, because MySpecificClass has overwritten it.

This is complex stuff. You're very smart being sure to do three generations (MySuperClass, MyBaseClass, and MySpecificClass), because it's really easy to do this for just a two-level hierarchy, but for three+ levels, it's much more complicated. :-)

If you want a thorough discussion of dealing with inheritance, calling into superclass methods, etc., in JavaScript, I've written an article on it, and written a toolkit for doing it. Reading the article and looking at the toolkit source (which goes beyond the article) may be useful in understanding how the prototype chain works and how to work with it.

Here's an example not using any toolkit and not trying to make supercalls easy. To keep things clear, I've used the terms Parent, Child, and GrandChild for the three generations:

// A parent (base) "class"
function Parent(a) {
  this.a = a;
}
Parent.prototype.one = function() {
  console.log("I'm Parent#one: a = " + this.a);
};
Parent.prototype.two = function() {
  console.log("I'm Parent#two: a = " + this.a);
};

// A child "subclass"
function Child(a, b) {
  // Chain to "superclass" constructor
  Parent.call(this, a);

  // Do our own init
  this.b = b;
}

// Create the prototype objct that `new Child` will assign to instances
// by creating a blank object backed by `Parent.prototype`. Also set
// the `constructor` property on the object; JavaScript defines that it
// will refer back to the function on the default prototype objects, so
// we do that for consistency despite nothing in JavaScript actually
// _using_ `constructor`.
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

// Add things to `Child.prototype`
Child.prototype.one = function() {
  Parent.prototype.one.call(this);
  console.log("I'm Child#one: b = " + this.b);
};
Child.prototype.three = function() {
  console.log("I'm Child#three: b = " + this.b);
};

// A grandchild "subclass"
function GrandChild(b, c) {
  // Chain to "superclass" constructor
  // Note that GrandChild has a fixed value for Parent's `a`
  Child.call(this, "GrandChildFixedA", b);

  // Do our own init
  this.c = c;
}

// Again create a blank object to be the prototype `new GrandChild`
// assigns, again set `constructor`
GrandChild.prototype = Object.create(Child.prototype);
GrandChild.prototype.constructor = GrandChild;

// Add things to it
GrandChild.prototype.one = function() {
    Child.prototype.one.call(this);
    console.log("I'm GrandChild#one: c = " + this.c);
};
GrandChild.prototype.three = function() {
    Child.prototype.three.call(this);
    console.log("I'm GrandChild#three: c = " + this.c);
};

Usage:

var p = new Parent("ParentA");
console.log("Calling p.one");
p.one();    // "I'm Parent#one: a = ParentA"
console.log("Calling p.two");
p.two();    // "I'm Parent#two: a = ParentA"
var c = new Child("ChildA", "ChildB");
console.log("Calling c.one");
c.one();    // "I'm Parent#one: a = ChildA" then "I'm Child #one: b = ChildB"
console.log("Calling c.two");
c.two();    // "I'm Parent#two: a = ChildA"
console.log("Calling c.three");
c.three();  // "I'm Child#three: b = ChildB"
var gc = new GrandChild("GrandChildB", "GrandChildC");
console.log("Calling gc.one");
gc.one();   // "I'm Parent#one: a = GrandChildFixedA" then "I'm Child #one: b = GrandChildB" then "I'm GrandChild#one: c = GrandChildC"
console.log("Calling gc.two");
gc.two();   // "I'm Parent#two: a = GrandChildA"
console.log("Calling gc.three");
gc.three(); // "I'm Child#three: b = GrandChildB" then "I'm GrandChild#three: c = GrandChildC"

Testing instanceof, although if you're using instanceof a lot, you might want to read up on duck typing:

// Some things that should be true
console.log("p instanceof Parent? " + (p instanceof Parent));
console.log("c instanceof Parent? " + (c instanceof Parent));
console.log("c instanceof Child? "  + (c instanceof Child));
console.log("gc instanceof Parent? " + (gc instanceof Parent));
console.log("gc instanceof Child? "  + (gc instanceof Child));
console.log("gc instanceof GrandChild? "  + (gc instanceof GrandChild));

// And some things that *shouldn't* be true:
console.log("p instanceof Child? (should be false) " + (p instanceof Child));
console.log("p instanceof GrandChild? (should be false) " + (p instanceof GrandChild));
console.log("c instanceof GrandChild? (should be false) " + (c instanceof GrandChild));

If you're not in an ES5-enabled environment, you can use this shim for Object.create (note: not a complete shim, just enough to enable the above):

Object.create = function(p) {
  var o;

  function ctor() {
  }

  ctor.prototype = p;

  o = new ctor();

  ctor.prototype = null;
  return o;
};

You can see why a toolkit script makes life a bit easier. You have several to choose from. Here's what the above looks like using Lineage, my toolkit:

// A parent (base) "class"
var Parent = Lineage.define(function(p) {
  p.initialize = function(a) {
    this.a = a;
  };
  p.one = function() {
    console.log("I'm Parent#one: a = " + this.a);
  };
  p.two = function() {
    console.log("I'm Parent#two: a = " + this.a);
  };
});

// A child "subclass"
var Child = Lineage.define(Parent, function(p, pp) {
  p.initialize = function(a, b) {
    // Chain to "superclass" constructor
    pp.initialize.call(this, a);

    // Do our own init
    this.b = b;
  };
  p.one = function() {
    pp.one.call(this);
    console.log("I'm Child#one: b = " + this.b);
  };
  p.three = function() {
    console.log("I'm Child#three: b = " + this.b);
  };
});

// A grandchild "subclass"
var GrandChild = Lineage.define(Child, function(p, pp) {
  p.initialize = function(b, c) {
    // Chain to "superclass" constructor
    // Note that GrandChild has a fixed value for Parent's `a`
    pp.initialize.call(this, "GrandChildFixedA", b);

    // Do our own init
    this.c = c;
  };
  p.one = function() {
      pp.one.call(this);
      console.log("I'm GrandChild#one: c = " + this.c);
  };
  p.three = function() {
      pp.three.call(this);
      console.log("I'm GrandChild#three: c = " + this.c);
  };
});

Usage is the same.

ES2015 and later

As of ES2015 (aka "ES6"), JavaScript got the class and super keywords, which dramatically simplify the above, and can be used today with transpiling.

class Parent {
    constructor(a) {
        this.a = a;
    }

    one() {
        console.log("I'm Parent#one: a = " + this.a);
    }

    two() {
        console.log("I'm Parent#two: a = " + this.a);
    }
}

class Child extends Parent {
    constructor(a) {
        super(a);
    }

    one() {
        super.one();
        console.log("I'm Child#one: a = " + this.a);
    }

    three() {
        console.log("I'm Child#three: a = " + this.a);
    }
}

class GrandChild extends Child {
    constructor(a) {
        super(a);
    }

    one() {
        super.one();
        console.log("I'm GrandChild#one: a = " + this.a);
    }

    three() {
        super.three();
        console.log("I'm GrandChild#three: a = " + this.a);
    }
}

// Usage
var p = new Parent("ParentA");
console.log("Calling p.one");
p.one();    // "I'm Parent#one: a = ParentA"
console.log("Calling p.two");
p.two();    // "I'm Parent#two: a = ParentA"
var c = new Child("ChildA", "ChildB");
console.log("Calling c.one");
c.one();    // "I'm Parent#one: a = ChildA" then "I'm Child #one: b = ChildB"
console.log("Calling c.two");
c.two();    // "I'm Parent#two: a = ChildA"
console.log("Calling c.three");
c.three();  // "I'm Child#three: b = ChildB"
var gc = new GrandChild("GrandChildB", "GrandChildC");
console.log("Calling gc.one");
gc.one();   // "I'm Parent#one: a = GrandChildFixedA" then "I'm Child #one: b = GrandChildB" then "I'm GrandChild#one: c = GrandChildC"
console.log("Calling gc.two");
gc.two();   // "I'm Parent#two: a = GrandChildA"
console.log("Calling gc.three");
gc.three(); // "I'm Child#three: b = GrandChildB" then "I'm GrandChild#three: c = GrandChildC"

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...