Reputation: 2164
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 SubA1
with prototype SubA
with 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]
.
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.
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
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 type
an 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