JS OO Pattern with Prototype and Base Class

前端 未结 2 949
情歌与酒
情歌与酒 2020-12-12 08:30

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;
         


        
相关标签:
2条回答
  • 2020-12-12 09:03

    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"
    
    0 讨论(0)
  • 2020-12-12 09:09

    I'm using this approach:

    var func1 = function(parameter1, parameter2) {
        // do your stuff here
    }
    
    var func2 = function(parameter1, parameter2, parameter3) {
        // call the constructor of func1 with actual 'this'
        func1.call(this, parameter1, parameter2);
    
        // do your specific task here
    }
    
    func2.prototype = func1.prototype;
    func2.prototype.constructor = func2;
    

    works fine :)

    0 讨论(0)
提交回复
热议问题