NanoWizard
NanoWizard

Reputation: 2164

Javascript: How to get a list of an object's prototype chain constructor names?

I recently encountered an interesting problem. I am developing with some existing code where, for better or worse, there are often several levels of prototypical inheritance ending in constructors for all kinds of various objects. Each of these ultimately inherit from a single base 'class'.

Each constructor follows the following inheritance pattern, where Sub is a subclass and Super is it's immediate prototype:

function Sub(/*args*/) {
    Super.call(this,/*args*/);
}

Sub.prototype = Object.create(Super.prototype);

This pattern continues all the way up to the single base class.

Discovering that it would be incredibly beneficial for our needs to store a list of constructor names for each object up the prototype chain, I came up with the following code addition for the base class, which uses the callee and caller properties of the arguments object:

var curConstructor = arguments.callee;
this.constructorList = [curConstructor.name];

while(curConstructor.caller && curConstructor.caller.name !== ''){
    curConstructor = curConstructor.caller;
    this.constructorList.push(curConstructor.name);
}

The main issue with this, is it only works if the object was constructed inside of an anonymous function (or global scope). For instance, assuming I have a constructor SubA1with prototype SubAwith prototype Base, if I do this:

function() {
  var aOk = new SubA1();
  function oops() {
    var aNotOk = new SubA1();
  }
  oops();
}

aOk's constructor list is: [Base,SubA,SubA1], which is what I want.

aNotOk's constructor list ends up as: [Base,SubA,SubA1,oops].

Here's a plunker.

I could fix it by having each constructor add their name to the constructor list themselves, but there are several problems with that, not least of which is the tremendous pain (and debugging / maintenance nightmare) it would be to add code to each of the many many constructor functions.

Is there an elegant way to achieve this?

Note that although the above question was asked in a more general sense, and not specific to Node.js, I am actually working with code in a Node.js environment. Therefore, solutions specific to Node are welcome.

Update:

In my particular case, all of the constructors are within a namespace. So I can actually solve the problem by performing a check to make sure that the function name exists in the namespace before adding it to the constructorList.

Basically, I can do this:

var curConstructor = arguments.callee;
this.constructorList = [curConstructor.name];

while(curConstructor.caller){
    curConstructor = curConstructor.caller;

    // Exit if the function name isn't part of the namespace
    if(typeof namespace[curConstructor.name] !== 'function') break;

    this.constructorList.push(curConstructor.name);
}

This won't solve it in a generic sense though of course, and it isn't perfect. If the function which calls new SubA1 for example just happens to have the same name as one of the constructors in the namespace, it will throw the list off. In my particular case, since the constructors follow a unique naming pattern, that occurrence is extremely unlikely.

I am aware that arguments.callee is deprecated, and that Object.constructor is not trustworthy (it doesn't get me what I want anyway), but I am unaware of any other way to get names of each constructor in the chain by modifying the base 'class' only.

Upvotes: 4

Views: 3159

Answers (1)

Martin Ernst
Martin Ernst

Reputation: 3279

This function gets all constructors of a prototype chain, when they are registered as a property of the prototype:

function getConstructorChain(obj, type) {
    var cs = [], pt = obj;
    do {
       if (pt = Object.getPrototypeOf(pt)) cs.push(pt.constructor || null);
    } while (pt != null);
    return type == 'names' ? cs.map(function(c) {
        return c ? c.toString().split(/\s|\(/)[1] : null;
    }) : cs;
}

Without argument typean array of functions is returned, with type = 'names' an array of function-name strings.

Working FIDDLE with a HTMLDivElement object as example here. Use console.

Upvotes: 2

Related Questions