Javascript “OOP” and prototypes with multiple-level inheritance

后端 未结 3 928
忘了有多久
忘了有多久 2020-12-02 13:48

I\'m new to Javascript programming and I\'m approaching my first application (a game, indeed) from an object oriented programming perspective (I know js is not really object

相关标签:
3条回答
  • 2020-12-02 14:23

    In OOP, when you're are defining a constructor of a sub class you are also (implicitly or explicitly) choosing a constructor from the super type. When a sub object is constructed both constructor are executed, first that from the super class, the all the other down the hierarchy.

    In javascript this must be explicitly called and don't come automatically!

    function Thing() {
      this.relatedThings = [];
    }
    Thing.prototype.relateThing = function(what){
      this.relatedThings.push(what);
    }
    
    function ThingA(){
      Thing.call(this);
    }
    ThingA.prototype = new Thing();
    
    function ThingA1(){
      ThingA.call(this);
    }
    ThingA1.prototype = new ThingA();
    
    function ThingA2(){
      ThingA.call(this);
    
    }
    ThingA2.prototype = new ThingA();
    

    If you don't this way, all the instance of ThingA, ThingA1 and ThingA2 shere the same relatedThings array constructed in the Thing constructor and called once for all instance at line:

    ThingA.prototype = new Thing();
    

    Globally, infact, in your code, you have only 2 calls to the Thing constructor and this result in only 2 instances of the relatedThings array.

    0 讨论(0)
  • 2020-12-02 14:29

    If you don't like the way prototyping works in JavaScript in order to achieve a simple way of inheritance and OOP, I'd suggest taking a look at this: https://github.com/haroldiedema/joii

    It basically allows you to do the following (and more):

    // First (bottom level)
    var Person = new Class(function() {
        this.name = "Unknown Person";
    });
    
    // Employee, extend on Person & apply the Role property.
    var Employee = new Class({ extends: Person }, function() {
        this.name = 'Unknown Employee';
        this.role = 'Employee';
    });
    
    // 3rd level, extend on Employee. Modify existing properties.
    var Manager = new Class({ extends: Employee }, function() {
    
        // Overwrite the value of 'role'.
        this.role = 'Manager';
    
        // Class constructor to apply the given 'name' value.
        this.__construct = function(name) {
            this.name = name;
        }
    });
    
    // And to use the final result:
    var myManager = new Manager("John Smith");
    console.log( myManager.name ); // John Smith
    console.log( myManager.role ); // Manager
    
    0 讨论(0)
  • 2020-12-02 14:31

    Welcome to the prototype chain!

    Let's see what it looks like in your example.

    The Problem

    When you call new Thing(), you are creating a new object with a property relatedThings which refers to an array. So we can say we have this:

    +--------------+
    |Thing instance|
    |              |
    | relatedThings|----> Array
    +--------------+     
    

    You are then assigning this instance to ThingA.prototype:

    +--------------+
    |    ThingA    |      +--------------+
    |              |      |Thing instance|
    |   prototype  |----> |              |
    +--------------+      | relatedThings|----> Array
                          +--------------+
    

    So each instance of ThingA will inherit from the Thing instance. Now you are going to create ThingA1 and ThingA2 and assign a new ThingA instance to each of their prototypes, and later create instances of ThingA1 and ThingA2 (and ThingA and Thing, but not shown here).

    The relationship is now this (__proto__ is an internal property, connecting an object with its prototype):

                                   +-------------+
                                   |   ThingA    |
                                   |             |    
    +-------------+                |  prototype  |----+
    |   ThingA1   |                +-------------+    |
    |             |                                   |
    |  prototype  |---> +--------------+              |
    +-------------+     |    ThingA    |              |
                        | instance (1) |              |
                        |              |              |
    +-------------+     |  __proto__   |--------------+ 
    |   ThingA1   |     +--------------+              |
    |   instance  |           ^                       |
    |             |           |                       v
    |  __proto__  |-----------+                 +--------------+
    +-------------+                             |Thing instance|
                                                |              |
                                                | relatedThings|---> Array
    +-------------+     +--------------+        +--------------+ 
    |   ThingA2   |     |   ThingA     |              ^
    |             |     | instance (2) |              |
    |  prototype  |---> |              |              |
    +-------------+     |  __proto__   |--------------+
                        +--------------+
    +-------------+           ^
    |   ThingA2   |           |  
    |   instance  |           |
    |             |           |
    |  __proto__  |-----------+
    +-------------+                        
    

    And because of that, every instance of ThingA, ThingA1 or ThingA2 refers to one and the same array instance.

    This is not what you want!


    The Solution

    To solve this problem, each instance of any "subclass" should have its own relatedThings property. You can achieve this by calling the parent constructor in each child constructor, similar to calling super() in other languages:

    function ThingA() {
        Thing.call(this);
    }
    
    function ThingA1() {
        ThingA.call(this);
    }
    
    // ...
    

    This calls Thing and ThingA and sets this inside those function to the first argument you pass to .call. Learn more about .call [MDN] and this [MDN].

    This alone will change the above picture to:

                                   +-------------+
                                   |   ThingA    |
                                   |             |    
    +-------------+                |  prototype  |----+
    |   ThingA1   |                +-------------+    |
    |             |                                   |
    |  prototype  |---> +--------------+              |
    +-------------+     |    ThingA    |              |
                        | instance (1) |              |
                        |              |              |
                        | relatedThings|---> Array    |
    +-------------+     |  __proto__   |--------------+ 
    |   ThingA1   |     +--------------+              |
    |   instance  |           ^                       |
    |             |           |                       |
    |relatedThings|---> Array |                       v
    |  __proto__  |-----------+                 +--------------+
    +-------------+                             |Thing instance|
                                                |              |
                                                | relatedThings|---> Array
    +-------------+     +--------------+        +--------------+ 
    |   ThingA2   |     |   ThingA     |              ^
    |             |     | instance (2) |              |
    |  prototype  |---> |              |              |
    +-------------+     | relatedThings|---> Array    |
                        |  __proto__   |--------------+
                        +--------------+
    +-------------+           ^
    |   ThingA2   |           |  
    |   instance  |           |
    |             |           |
    |relatedThings|---> Array | 
    |  __proto__  |-----------+
    +-------------+
    

    As you can see, each instance has its own relatedThings property, which refers to a different array instance. There are still relatedThings properties in the prototype chain, but they are all shadowed by the instance property.


    Better Inheritance

    Also, don't set the prototype with:

    ThingA.prototype = new Thing();
    

    You actually don't want to create a new Thing instance here. What would happen if Thing expected arguments? Which one would you pass? What if calling the Thing constructor has side effects?

    What you actually want is to hook up Thing.prototype into the prototype chain. You can do this with Object.create [MDN]:

    ThingA.prototype = Object.create(Thing.prototype);
    

    Anything that happens when the constructor (Thing) is executed will happen later, when we actually create a new ThingA instance (by calling Thing.call(this) as shown above).

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