Thursday, June 19, 2008

Has this constructor been prototyped?

This is a quick post about libraries that uses native constructor in an obtrusive way.
Using a Function prototype, that sounds like a non-sense, it is possible to know if a constructor has some property, or method, defined in its prototype.

Function.prototype.prototyped = function(){
for(var i in new this)
return true;
return false;
};


Some test example?

alert(Array.prototyped()); // false

Object.prototype.each = function(){};
alert(Array.prototyped()); // true

delete Object.prototype.each;
alert(Array.prototyped()); // false

Array.prototype.each = function(){};
alert(Array.prototyped()); // true
alert(Object.prototyped()); // false


Update
Has Kangas spotted, there is no reason to create a new instance.
We could loop directly the prototype object.
But what's up if we have injected privileged methods in the constructor?

Array = function(Array){
var prototype = Array.prototype;
return function(){
arguments = prototype.slice.call(arguments, 0);
arguments.each = function(){
prototype.forEach.apply(this, arguments);
};
return arguments;
};
}(Array);

var a = new Array(1, 2, 3);
a.each(function(value){
alert(value)
});

for(i in Array.prototype)
alert(i); // nothing

Above example is the reason I chose to create an instance ... but hey, why on heart someone should wrap a native constructor? :P

7 comments:

Alsanan said...

Smart!

kangax said...

I don't see a reason to instantiate a constructor. We might as well iterate over its prototype:

Function.prototype.prototyped = function(){
for (var prop in this.prototype)
return true;
return false;
}

Best,
kangax

Andrea Giammarchi said...

Good point kangax. I have updated the post explaining why I chose the new this, instead of this.prototype.

At the same time, the creation of that "empty instance", should not be a problem for memory leaks.

Anyway, if you want to use prototyped method with every kind of constructor, your solution, as far as I know, is definitively better, while mine, was dedicated to primitive constructor that do not necessary require arguments (Array, Boolean, Date, Function, Number, Object, String)

Regards

straytenebrae said...

Another unfortunate downside to performing an instantiation is if the constructor does work with arguments. Which seems like a reasonable concern if you are performing introspection at runtime.

Perhaps a try/catch block? Which still won't handle constructors with side effects...

The prototype inspection, while lossy, seems like a safer choice. Especially when modifying a base prototype.

Nice work,

s

Andrea Giammarchi said...

following prototypal inheritance, there is no reason to use slow try catch :)

Function.prototype.prototyped = function(){
var i = function(){};
i.prototype = this.prototype;
for(i in new i)
return true;
return false;
};


But above code is exactly the same of kangax one:
Function.prototype.prototyped = function(){
for(var i in this.prototype)
return true;
return false;
};


and I have already explained that I did not look for prototype because:
- the aim is to know if primitive constructors have been prototyped
- someone culd wrap a constructor injecting privileged methods or parameters

;)

kangax said...

Andrea,

I understand that it was intended for native constructors (there are no "primitive" constructors afaik :)) but I just don't see a reason to create a new instance of Array, Object, Date, etc. when iterating over their prototype does the same thing. If you frequently call #prototyped (in some loop), then you clobber memory with bunch of unnecessary objects. Yes, they are all being GC'ed right away, but you get the point ;)

kangax said...

I think that "injected privileged methods" is kind of an edge case :), but I see your point. Of course, "new Func" won't have same members as "Func.prototype" if Func explicitly returns an "alien" object (as in your example)