Kalreg
Kalreg

Reputation: 1012

javascript - prototype chain

Please consider such a code:

class a {
    constructor() {
        this.test = "test from a";
        this.testA = "X";
    }

    mimi() {
        console.log('aaaaaaaa', this.test, this.testA)
    }

}

class b extends a {
    constructor() {
        super();
        this.test = "test from b"
    }

    mimi() {
        console.log('bbbbbbbb', this.test, this.testA)
    }

}

class c extends b {
    constructor() {
        super();
        this.test = "test from c"
    }

    mimi() {
        console.log('cccccccc', this.test, this.testA)
    }

    meme() {
        var test = kalreg.__proto__.__proto__;
        test.mimi();
        var test2 = kalreg.__proto__.__proto__.__proto__;
        test2.mimi();
    }
}

var kalreg = new c(); kalreg.mimi(); kalreg.meme();

The output I get is:

cccccccc test from c X
bbbbbbbb undefined undefined
aaaaaaaa undefined undefined

My object logic makes me use "a" as the most generic class, "b" that is a child to it, and "c" that is the child to "b". I want "c" to have all methods and properties of "b" and "a" BUT part of functionalities of "a" is overwritten by "b" so the only way "c" may have access to overwritten functionalities is to use prototype chain. Unfortunately, my way doesn't work so the questions are:

  1. In meme() function, how to avoid kalreg.proto - I've been told that such way of accessing prototype is bad and dangerous for the code.
  2. In my opinion, there should be no "undefined" in output, however, there is. The expected output is:

    cccccccc test from c X

    bbbbbbbb test from b X

    aaaaaaaa test from a X

How to achieve it?

Thank you!

Upvotes: 3

Views: 78

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074178

in meme() function, how to avoid kalreg.proto

Use super for the use cases where you really do need to access a property from the superclass (but note that that won't help with test and testA, because those aren't on the superclass or its prototype object, they're on the instance created with new c; more on that below).

Separately: Avoid __proto__. In those rare cases where you need to access an object's prototype, use Object.getPrototypeOf.

in my opinion there should be no "undefined" in output however there is. expected output is:

cccccccc test from c X

bbbbbbbb test from b X

aaaaaaaa test from a X

The output is correct, because the prototype objects on which you're calling mimi don't have a test or testA property. Only the object created with new c has those properties. And there is only one such object, so test will be "test from c" no matter which mimi you call, and testA will always be "X".

In a comment you've asked how there can only be one test when each constructor has this.test = ... in it. Let's look at what's in memory once you've done var kalreg = new c();:

                                         +−−−−−−−−−−−−+
a−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−>| (function) |  +−−−>(Function.prototype)
                                      |  +−−−−−−−−−−−−+  |
                                      |  | __proto__  |−−+          +−−−−−−−−−−−−−+
                                      |  | prototype  |−−−−−−−−−−+−>|  (object)   |
                                      |  +−−−−−−−−−−−−+          |  +−−−−−−−−−−−−−+
                                      |                          |  | __proto__   |−−>...
                       +−−−−−−−−−−−−+ |                          |  | constructor |−−>a
b−−−−−−−−−−−−−−−−−−−+−>| (function) | |                          |  | mimi        |−−>...
                    |  +−−−−−−−−−−−−+ |                          |  +−−−−−−−−−−−−−+
                    |  | __proto__  |−+         +−−−−−−−−−−−−−+  |
                    |  | prototype  |−−−−−−−−+−>|  (object)   |  |
                    |  +−−−−−−−−−−−−+        |  +−−−−−−−−−−−−−+  |
                    |                        |  | __proto__   |−−+
     +−−−−−−−−−−−−+ |                        |  | constructor |−−>b
c−−−>| (function) | |                        |  | mimi        |−−>...
     +−−−−−−−−−−−−+ |                        |  +−−−−−−−−−−−−−+
     | __proto__  |−+        +−−−−−−−−−−−−−+ |
     | prototype  |−−−−−−−+−>|  (object)   | |
     +−−−−−−−−−−−−+       |  +−−−−−−−−−−−−−+ |
                          |  | __proto__   |−+
                          |  | constructor |−−>c
          +−−−−−−−−−−−−+  |  | mimi        |−−>...
kalreg−−−>|  (object)  |  |  | meme        |−−>...
          +−−−−−−−−−−−−+  |  +−−−−−−−−−−−−−+
          | __proto__  |−−+
          | test       |
          | testA      |
          +−−−−−−−−−−−−+

As you can see, only the object kalreg points to has test or testA. Why? Because during the call to each constructor, this refers to that object; that's how new and super() work. So since this refers to that object, each constructor does its this.test = ... line, and since the one in c is the last one to run, it "wins."

You can access superclass versions of the mimi method, but since they all show the test property, they'll all show "test from c". To show different things, they'd have to have different properties to show. Additionally, with super you can only go one level up, so if you wanted to go two levels up, you'd either use a.prototype.mimi (or this.__proto__.__proto__.mimi explicitly, or put some facility in b to call a's mimi.

Example with different properties for each level, and with b providing superMimi so c can use a's mimi:

class a {
    constructor() {
        this.testA = "test from a (this.testA)";
    }

    mimi() {
        console.log('aaaaaaaa', this.testA);
    }
}

class b extends a {
    constructor() {
        super();
        this.testB = "test from b (this.testB)";
    }

    mimi() {
        console.log('bbbbbbbb', this.testB)
    }
    
    superMimi() {
      return super.mimi();
    }
}

class c extends b {
    constructor() {
        super();
        this.testC = "test from c (this.testC)";
    }

    mimi() {
        console.log('cccccccc', this.testC);
    }

    meme() {
        super.mimi();      // Uses b's version
        super.superMimi(); // Uses a's version
        this.__proto__.__proto__.__proto__.mimi.call(this); // Also uses a's version
        var p = Object.getPrototypeOf(this);
        var p2 = Object.getPrototypeOf(p);
        var p3 = Object.getPrototypeOf(p2);
        p3.mimi.call(this); // Also uses a's version
    }
}

var kalreg = new c();
kalreg.mimi();
kalreg.meme();

Upvotes: 2

Related Questions