Search code examples
javascriptinheritancemethodsprototypeprototypal-inheritance

How can I get all the ancestors' methods in JS (prototypal inheritance)?


I need to create a function in JS that gets all the methods from the whole inheritance tree so as to insert one <button> for each method. I have this code

function A () {
}

A.prototype.a = function () {
    console.log('a');
}

function B () {
    this.super = A;
    this.super();
}

B.prototype = new A;
B.prototype.constructor = B;

B.prototype.b = function () {
    console.log('b');
}

function C () {
    this.super = B;
    this.super();
}

C.prototype = new B;
C.prototype.constructor = C;

C.prototype.c = function () {
    console.log('c');
}

function D () {
}

D.prototype = C;
D.prototype.constructor = D;

D.prototype.d = function () {
    console.log('d');
}

const dd = new D();

I need some way to find the methods of the whole tree in case I didn't know how many ancestors an object has. For example: If an object C had A and B as its ancestors, I need the methods from A, B, and C; If a D object were a child of A-B-C, I need the methods of them four (A, B, C, and D). I may be able to trace by hand each method so as to write a code that does that for me, buy I need it to be dynamic.

This is what I'm using:

console.log(Object.getOwnPropertyNames(dd.__proto__).filter(function (property) {
    return (
        typeof dd.__proto__[property] === 'function' && property !== 'constructor'
    );
}))

Solution

  • There is a mistake in your setup-code:

    Change:

    D.prototype = C;
    

    to:

    D.prototype = new C;
    

    Then to get the list of methods (even when not enumerable), make a recursive call. You can even make it a generator.

    To make clear where the methods come from, I have prefixed them with the name of the prototype object (like A.prototype). You will see that when functions with the same name exist at multiple levels of the prototype chain, they get each listed:

    function * iterMethods(o) {
        if (!o || o === Object.prototype) return;
        for (let name of Object.getOwnPropertyNames(o)) {
            try {
                if (name !== "constructor" && typeof o[name] === "function") {
                    yield o.constructor.name + ".prototype." + name;
                }
            } catch {}
        }
        yield * iterMethods(Object.getPrototypeOf(o));
    }
    
    // Your code:
    function A () {
    }
    
    A.prototype.a = function () {
        console.log('a');
    }
    
    function B () {
        this.super = A;
        this.super();
    }
    
    B.prototype = new A;
    B.prototype.constructor = B;
    
    B.prototype.b = function () {
        console.log('b');
    }
    
    function C () {
        this.super = B;
        this.super();
    }
    
    C.prototype = new B;
    C.prototype.constructor = C;
    
    C.prototype.c = function () {
        console.log('c');
    }
    
    function D () {
    }
    
    D.prototype = new C;
    D.prototype.constructor = D;
    
    D.prototype.d = function () {
        console.log('d');
    }
    
    const dd = new D();
    
    // The call:
    console.log(Array.from(iterMethods(Object.getPrototypeOf(dd))));

    It is easier to set up this prototypal hierarchy when you use the class ... extends syntax. I would also not define super as that is a keyword that is natively available. Here is how that would look:

    function * iterMethods(o) {
        if (!o || o === Object.prototype) return;
        for (let name of Object.getOwnPropertyNames(o)) {
            try {
                if (name !== "constructor" && typeof o[name] === "function") {
                    yield o.constructor.name + ".prototype." + name;
                }
            } catch {}
        }
        yield * iterMethods(Object.getPrototypeOf(o));
    }
    
    // class syntax version:
    class A {
        a() {
            console.log('a');
        }
    }
    
    class B extends A {
        b() {
            console.log('b');
        }
    }
    
    class C extends B {
        c() {
            console.log('c');
        }
    }
    
    class D extends C {
        d() {
            console.log('d');
        }
    }
    
    const dd = new D();
    
    // The call:
    console.log(Array.from(iterMethods(Object.getPrototypeOf(dd))));