Within a Javascript function it is a rather simple detection whether the function has been simply executed or being executed as an object instance constructor (using new
keyword).
// constructor
function SomeType() {
if (this instanceof SomeType)
// called as an object instance constructor
else
// the usual function call
}
That's fine, this has been answered here at SO at least a few times before.
So let's suppose now that our constructor function calls another function that I defined directly on Function prototype and is therefore accessible to all functions - the main purpose why I'm doing it this way.
Function.prototype.doSomething = function doSomething() {
// what code here?
};
// constructor
function SomeType() {
SomeType.doSomething();
}
Main problem
How can we now detect within doSomething
the same for SomeType
function?
The reason why I'd like to detect it is I'm writing a function that adopts/injects constructor parameters as constructed object instance members with the same name. Of course this function should only execute when it's being called by a constructor function and not by a function called regularly.
This is my answer to another question where you can see my adoptArguments
function that puts object constructor arguments into constructed object instance as members.
Workaround that enforces specific usage = bad
I have a possible workaround that I don't want to use, because it enforces correct usage - execution context injection. This is the code that can detect object instance constructor execution:
Function.prototype.doSomething = function doSomething() {
if (this instanceof doSomething.caller)
{
// object instance construction
}
else return; // nope, just normal function call
};
// constructor
function SomeType() {
// required use of ".call" or ".apply"
SomeType.doSomething.call(this);
}
This idea may spark some ideas of your own to solve the original problem
One possible solution is passing the constructor context as parameter. There is no need to pass in the arguments object as it can be accessed through this.arguments
, as you are doing in adoptArguments
on your linked answer.
This solution makes sense for me as I expect Function.prototype.someMethod
to be
called withing the context of a Function
instance rather than other context
(i.e., the newly created instance).
Function.prototype.doSomethingWith = function doSomethingWith(instance) {
if( instance instanceof this ) // proceed
};
// constructor
function SomeType() {
SomeType.doSomethingWith(this);
}
WARN: your adoptArguments
function has a serious bug, see below
var comments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var parser = /^function[^\(]*\(([^)]*)\)/mi;
var splitter = /\s*,\s*/mi;
Function.prototype.adoptArguments = function() {
var args;
// remove code comments
args = this.toString().replace(comments, "");
// parse function string for arguments
args = parser.exec(args)[1];
if (!args) return; // empty => no arguments
// get individual argument names
args = args.split(splitter);
// adopt all as own prototype members
for(var i = 0, len = args.length; i < len; ++i)
{
this.prototype[args[i]] = this.arguments[i];
}
};
console.log('the problem with your implementation:');
console.log('> adopting arguments as prototype members');
console.log('> implies you override values for every instance of YourType');
function YourType(a, b, c) {
YourType.adoptArguments();
}
var foo = new YourType( 1, 2, 3 );
console.log( 'foo', foo.a, foo.b, foo.c ); // foo 1 2 3
var bar = new YourType( 4, 5, 6 );
console.log( 'foo', foo.a, foo.b, foo.c ); // foo 4 5 6
console.log( 'bar', bar.a, bar.b, bar.c ); // bar 4 5 6
console.log();
console.log('also, a trim is need because:');
function OtherType( a, b, c ) { // see where whitespaces are
OtherType.adoptArguments();
}
var baz = new OtherType( 1, 2, 3 );
console.log( 'baz', baz.a, baz.b, baz.c );
// baz undefined 2 undefined
//
// My solution
//
console.log();
console.log('results');
// slighly modified from your adoptArguments function
Function.prototype.injectParamsOn = function injectParamsOn( instance ) {
// you may check `instance` to be instanceof this
if( ! (instance instanceof this) ) return;
// proceed with injection
var args;
// remove code comments
args = this.toString().replace(comments, "");
// parse function string for arguments
args = parser.exec(args)[1];
if (!args) return; // empty => no arguments
// get individual argument names (note the trim)
args = args.trim().split(splitter);
// adopt all as instance members
var n = 0;
while( args.length ) instance[ args.shift() ] = this.arguments[ n++ ];
};
function MyType( a, b, c ){
MyType.injectParamsOn( this );
}
var one = new MyType( 1, 2, 3 );
console.log( 'one', one.a, one.b, one.c ); // one 1 2 3
var two = new MyType( 4, 5, 6 );
console.log( 'one', one.a, one.b, one.c ); // one 1 2 3
console.log( 'two', two.a, two.b, two.c ); // two 4 5 6
var bad = MyType( 7, 8, 8 );
// this will throw as `bad` is undefined
// console.log( 'bad', bad.a, bad.b, bad.c );
console.log( global.a, global.b, global.c );
// all undefined, as expected (the reason for instanceof check)
Within a Javascript function it is a rather simple detection whether the function has been simply executed or being executed as an object instance constructor (using new keyword).
Actually, that's impossible, one cannot know in JS whether an user function was called as a constructor. The this instanceof
test is sufficient for the usual cases, but only checks whether the context does inherit from the class's prototype.
How can we now detect within doSomething
the same for SomeType
function?
You cannot for the same reason, and you cannot do the instanceof
test without passing this
as a parameter to your doSomething
.
Main problem: I'm writing a function that adopts/injects constructor parameters as constructed object instance members with the same name.
I recommend not to do so via a function call inside the constructor. Instead, try to decorate the constructor function, so that you will have access to all the values that you need right away:
Function.prototype.adoptArguments = function() {
var init = this;
var args = arguments.length ? arguments : init.toString().replace(comments, "").match(argumentsparser);
if (!args || !args.length) return init;
var constructor = function() {
if (this instanceof constructor) {
for (var i=0; i<args.length; i++)
this[args[i]] = arguments[i];
init.apply(this, arguments);
} else {
// throw new Error("must be invoked with new");
}
};
return constructor;
};
Then instead of
function SomeType() {
SomeType.adoptArguments();
}
do
var SomeType = function() {
}.adoptArguments();