JavaScript Inheritance with Prototypes — 'constructor' property?

前端 未结 4 1877
孤城傲影
孤城傲影 2020-12-25 13:40

I\'ve seen a lot of stuff like this, and am looking for the proper solution to basic JavaScript inheritance:

function Food(){}  // Food  constructor (class)
         


        
相关标签:
4条回答
  • 2020-12-25 14:25

    Your only problem in your logic was setting the same object basicFood to both Bread.prototype and Sushi.prototype. Try to do something like this:

    Bread.prototype = new Food();
    Bread.prototype.constructor = Bread;
    
    Sushi.prototype = new Food();
    Sushi.prototype.constructor = Sushi;
    

    Now the instanceof bread and sushi will be Food but the constructors will be Bread and Sushi for each of them in particular;

    0 讨论(0)
  • 2020-12-25 14:33

    Lets examine your code a little bit.

    function Food(){}
    function Bread(){}
    function Sushi(){}
    var basicFood = new Food();
    Bread.prototype = basicFood;
    Sushi.prototype = basicFood;
    

    Note: When you set the same object as the prototype of two objects, augmentation in one prototype, will reflect in the other prototype as well. For example,

    Bread.prototype = basicFood;
    Sushi.prototype = basicFood;
    Bread.prototype.testFunction = function() {
        return true;
    }
    console.log(Sushi.prototype.testFunction()); // true
    

    Lets get back to your questions.

    var bread = reconstructify(new Bread(), Bread);
    var sushi = reconstructify(new Sushi(), Sushi);
    console.log(sushi instanceof Bread);    // Why true?
    console.log(bread instanceof Sushi);    // Why true?
    

    As per the instanceof docs from MDN,

    The instanceof operator tests whether an object has in its prototype chain the prototype property of a constructor.

    So when we do something like

    object1 instanceof object2
    

    JavaScript will try to find if the prototype of the object2 is in the prototype chain of object1.

    In this case, it will return true only when the Bread.prototype is in the prototype chain of sushi. We know that sushi is constructed from Sushi. So, it will take Sushi's prototype and check if it is equal to Bread's prototype. Since, they both point to the same basicFood object, that returns true. Same case for, bread instanceof Sushi as well.

    So, the right way to inherit would be, like this

    function Food()  {}
    function Bread() {}
    function Sushi() {}
    
    Bread.prototype = Object.create(Food.prototype);
    Bread.prototype.constructor = Bread;
    Sushi.prototype = Object.create(Food.prototype);
    Sushi.prototype.constructor = Sushi;
    
    var bread = new Bread();
    var sushi = new Sushi();
    
    console.log(sushi instanceof Bread);  // false
    console.log(bread instanceof Sushi);  // false
    console.log(sushi.constructor);       // [Function: Sushi]
    console.log(bread.constructor);       // [Function: Bread]
    console.log(sushi instanceof Food);   // true
    console.log(bread instanceof Food);   // true
    console.log(sushi instanceof Sushi);  // true
    console.log(bread instanceof Bread);  // true
    
    0 讨论(0)
  • 2020-12-25 14:34

    This is my personal solution, which I have developed from the combined wisdom nuggets of @thefourtheye, @FelixKling, @SeanKinsey, and even the antics of @helly0d:


    Simplest Solution:

    /** Food Class -- You can bite all foods **/
    function Food(){ this.bites = 0 };
    Food.prototype.bite = function(){ console.log("Yum!"); return this.bites += 1 };
    
    /** All Foods inherit from basicFood **/
    var basicFood = new Food();
    
    /** Bread inherits from basicFood, and can go stale **/
    function Bread(){
      Food.apply(this); // running food's constructor (defines bites)
      this.stale = false;
    };
    Bread.prototype = Object.create( basicFood );
    Bread.prototype.constructor = Bread; // just conventional
    Bread.prototype.goStale = function(){ return this.stale = true };
    
    /** Sushi inherits from basicFood, and can be cooked **/
    function Sushi(){
      Food.apply(this);
      this.raw = true;
    };
    Sushi.prototype = Object.create( basicFood );
    Sushi.prototype.constructor = Sushi;
    Sushi.prototype.cook = function(){ return this.raw = false };
    



    Advanced Methodology:

    It's better because it makes the constructor prototype property a non-enumerable.

    /** My handy-dandy extend().to() function **/
    function extend(source){
      return {to:function(Constructor){
        Constructor.prototype = Object.create(source);
        Object.defineProperty(Constructor.prototype, 'constructor', {
          enumerable:   false,
          configurable: false,
          writable:     false,
          value:        Constructor
        });
        return Constructor;
      }}
    };
    
    
    function Food(){ this.bites = 0 };
    Food.prototype.bite = function(){ console.log("Yum!"); return this.bites += 1 };
    var basicFood = new Food();
    
    
    var Bread = extend(basicFood).to(function Bread(){
      Food.apply(this);
      this.stale = false;
    });
    Bread.prototype.goStale = function(){ return this.stale = true };
    
    
    var Sushi = extend(basicFood).to(function Sushi(){
      Food.apply(this);
      this.raw = true;
    });
    Sushi.prototype.cook = function(){ return this.raw = false };
    



    Both methodologies above yield the same test results:

    var food  = new Food();
    var bread = new Bread();
    var sushi = new Sushi();
    
    console.log( food instanceof Food );   // true
    console.log( food instanceof Bread );  // false
    console.log( food instanceof Sushi );  // false
    
    console.log( bread instanceof Food );  // true
    console.log( bread instanceof Bread ); // true
    console.log( bread instanceof Sushi ); // false
    
    console.log( sushi instanceof Food );  // true
    console.log( sushi instanceof Bread ); // false
    console.log( sushi instanceof Sushi ); // true
    
    console.log( food.constructor );       // Food
    console.log( bread.constructor );      // Bread
    console.log( sushi.constructor );      // Sushi
    



    A very special thanks to @FelixKling, whose experience helped hone my understanding in the chat outside of this thread -- also to @thefourtheye, who was the first to show me the correct way -- and also to @SeanKinsey, who highlighted the usefulness of being able to run the parent constructor within the context of the children.

    I community wiki'd this answer -- please let me know or edit yourself if you find anything in this answer's code which is suspect :)

    0 讨论(0)
  • 2020-12-25 14:44

    What you are doing wrong is to reuse the basicFood object for multiple child 'classes'. Instead, new up a new one. That way, as you add members to the prototype (new instance of the parent), you're adding it to an instance that is not shared among other inheriting classes.

    Now, there's one thing that your code is lacking, and that is constructors without side effects. Many constructors requires arguments, and will throw without them - but how can you construct a prototype for a new descending class without new'ing up a parent? Well, we're not actually interested in the parent function, only in the parents prototype. So what you can do is

    function Parent() { /*some side effect or invariant */ }
    Parent.prototype.foo = ...
    function Child() { Parent.call(this); }
    
    // the next few lines typically go into a utility function
    function F() {} // a throw-away constructor
    F.prototype = Parent.prototype; // borrow the real parent prototype
    F.prototype.constructor = Parent; // yep, we're faking it
    Child.prototype = new F(); // no side effects, but we have a valid prototype chain
    Child.prototype.bar = ... // now continue adding to the new prototype
    
    0 讨论(0)
提交回复
热议问题