4

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

15
  • hmmm - if the workaround requires a specific invocation, you would be as well mandating that the construction check is made (rather than the call or apply style invocation) ? Commented Jan 8, 2015 at 11:19
  • @Woody: Well that's true if all that subfunction would do would be this check. but no. In my actual scenario, this doSomething function does other things as well as can be seen in this answer I've written to a different SO question. Commented Jan 8, 2015 at 11:35
  • So maybe understanding why you need to know the difference, in both cases, would lead to a solution. Right now it's pretty unclear what the motive is. Commented Jan 8, 2015 at 11:42
  • @MichaelPerrenoud: Ok. I edited my Main problem part to explain why I'd like to detect this... Commented Jan 8, 2015 at 12:07
  • "Of course this function should only execute when it's being called by a constructor function and not by a function called regularly.". You mean detecting wherever doSomething was called within the context of an instance of any type, not just SomeType? Commented Jan 8, 2015 at 12:15

2 Answers 2

2

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();
Sign up to request clarification or add additional context in comments.

15 Comments

Actually this is quite clever and plausible, although instead of completely overriding constructor with your own and always with the same name, it would be better to return a new function generated by new Function() that could change existing constructor's body and keep everything else. Something in this form. You'd actually end up with the same function but you'd inject additional statements in its body. The only problem that would need solving is the required assignment to a variable. I'm not sure why it's actually required?
Actually variable assignment requirement could be avoided during object instantiation doing var objInstance = new (SomeType.adoptArguments()); but it seems just as dodgy as your way although I do end up with original constructor name and changed function body.
What I mean by using new Function() is calling this in place of your new constructor generation: return new Function(args.join(","), "return function " + funcname + "(" + args.join() + ") {" + args.map(function(name, index){ return "\n\tthis." + name + " = " + name + ";" }).join("") + funcbody + "};")();
@Bergi My downvote is because your assertion "Actually, that's impossible, one cannot know in JS whether an user function was called as a constructor." is too bold. You are right that this instanceof x effectively does not ensure function x was called with the new keyword (simply checks this inherits from x.prototype), but that doesn't prove the previous assertion. Additionally you assert "You should not do so via a function call inside the constructor." without explaning "why?"
@laconbass: It may be bold, but it's correct: a user-defined function cannot distinguish between [[call]] and [[construct]] invocations in ES5. See also the question that I've linked now (see edit). The "you should not" was meant as a recommendation, as my proposal is less complicated (and imho, semantically cleaner with its functional style).
|
0

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)

7 Comments

Yes I was aware of the bug that can be resolved as you've done it by providing the context to the function.
@RobertKoritnik I added code to answer specifically to this question, but anyway I'm aware that It may not provide the kind of "magical" detection it seems you are searching for.
Notice that this.arguments is deprecated and does not work in strict mode.
@Bergi you're right, it's use can be bypassed providing the arguments object to the function, but I left it to keep the function as close as possible to the original posted by OP
Well not really. This heavily depends on minification service and its options. For instance the one used by Asp.net Minification and Bundling renames function parameters and removes those that are not used within function body. As I pass this and arguments to my function all parameters get stripped from constructor. Thats to my Angular app I'm not parsing function string but rather using $inject variable on my constructor. Handy.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.