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

Community
  • 1
  • 1
Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • 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) ? – Woody Jan 08 '15 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](http://stackoverflow.com/a/27838697/75642) to a different SO question. – Robert Koritnik Jan 08 '15 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. – Mike Perrenoud Jan 08 '15 at 11:42
  • @MichaelPerrenoud: Ok. I edited my **Main problem** part to explain why I'd like to detect this... – Robert Koritnik Jan 08 '15 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`? – laconbass Jan 08 '15 at 12:15
  • So I have to wonder if you were willing to compromise on the call to `adoptArguments` just a bit to something like `Person.adoptArguments(this, arguments)` if you couldn't achieve two things. First you'd have your caller via injection and you would no longer have to parse the text of the function to find the arguments. I know it means there is a convention to calling `adoptArguments`, but that's a function that performs a **very** specific task. – Mike Perrenoud Jan 08 '15 at 12:22
  • @laconbass: Can you provide an example of your statement "*within the context of an instance of any type*" please so I'm sure what you mean exactly by it. – Robert Koritnik Jan 08 '15 at 13:07
  • @RobertKoritnik "within the context"=`this` value inside `doSomething`; "an instance of any type"= `new SomeType()`, or maybe `new OtherType()`, or even `new CustomType()`. – laconbass Jan 08 '15 at 13:11
  • @laconbass: I know what you meant by *context* I just wasn't sure about the second part. As I added the `doSomething` directly on the `Function.prototype` I want to use it in virtually **any constructor**. Any of these constructors can have an arbitrary number of arguments. And that's what makes this function usable. If it was only one constructor I wouldn't even need this function. – Robert Koritnik Jan 08 '15 at 13:13
  • @MichaelPerrenoud: If I introduce just the *context* parameter to my function it can do it all. No need to inject arguments either. That's already given. But providing context is somewhat also specific usage (always have to do something) which is similar to using `.call` or `.apply`. It's true that function parameters are common usage compare to call/apply. So yes. Adding the context parameter does solve it as a workaround. But why would you also add arguments? YOu do realise that those are just values. I would still need to parse pargument names as I do now... – Robert Koritnik Jan 08 '15 at 13:29
  • @RobertKoritnik how you expect to access the constructor context without `.call`ing or `.apply`ing to `doSomething`? – laconbass Jan 08 '15 at 14:07
  • @laconbass: If we don't know something it doesn't mean it's impossible. Even though I know much about Javascript I also know I don't know everything... Hence my question. I'm 95% sure this isn't possible, but somebody may prove my assumptions wrong. – Robert Koritnik Jan 08 '15 at 14:16
  • @RobertKoritnik thanks for straightening me out on that arguments thing, my apologies. I had it in my head that it had the keys too. But I think Bergi has your solution! – Mike Perrenoud Jan 08 '15 at 16:19
  • @MichaelPerrenoud Bergi's solution would hide the real constructor function, this is specially bad when accessing `SomeType.constructor.name` property – laconbass Jan 08 '15 at 16:47
  • @RobertKoritnik After some thoughts about, I ended up concluding that makes more sense explicity passing the context as argument to `doSomething` rather than injecting it, as makes more sense calling `Function.prototype.thing` within the context of a `function`. – laconbass Jan 08 '15 at 16:49

2 Answers2

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();
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 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](http://stackoverflow.com/a/14178460/75642). 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? – Robert Koritnik Jan 09 '15 at 00:43
  • 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. – Robert Koritnik Jan 09 '15 at 00:47
  • 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 + "};")();` – Robert Koritnik Jan 09 '15 at 00:52
  • One cannot change (modify) a function in JS, therefore you need to overwrite it. As we can't use a function declaration, the assignment to a variable is necessary. I'm not sure why you care about the function name that much, though it's certainly [possible with eval magic](http://stackoverflow.com/q/9479046/1048572). Changing the way the constructor needs to be called is absolutely undesirable. Rewriting the complete function body won't work with closures, except you return a string and `eval` that in the appropriate scope. – Bergi Jan 09 '15 at 01:29
  • Terminology aside (I know we can't change function but rather replace it) variable assignment can be avoided by doing it this way: `function SomeType() {} SomeType = SomeType.adoptArguments();` By doing this **right after constructor declaration** you can still add prototype members afterwards. Actually an additional statement after constructor. – Robert Koritnik Jan 09 '15 at 01:42
  • uh, `SomeType = …` *is* a variable assignment. Except using `eval` with another function declaration, you cannot avoid the assignment. – Bergi Jan 09 '15 at 01:51
  • But it's different because we're actually **replacing constructor function** instead of changing/replacing variable value that points to a constructor function... So it is different. Replacing actual constructor function vs. variable value... – Robert Koritnik Jan 09 '15 at 02:00
  • 1
    @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 Jan 09 '15 at 03:09
  • @RobertKoritnik: I don't see any differences between the two assignments to the variable. Whether the variable was declared using a `var` statement or a `function` declaration doesn't really matter. Regardless, it seems cleaner to me to directly assign the "modified" function to the variable than to declare the function and change it *afterwards*. – Bergi Jan 09 '15 at 03:32
  • 1
    @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](http://stackoverflow.com/q/367768/1048572) 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). – Bergi Jan 09 '15 at 03:39
  • @Bergi: Well the difference is small but not insignificant. Hoisting... (although in this specific case it doesn't make any difference anyway). Normally in well maintainable and easy to use code you want to avoid using function expressions and rather use function declarations as they get hoisted so you can *hide* implementation details lower in your file, while keeping accessible stuff up top. – Robert Koritnik Jan 09 '15 at 03:50
  • This makes accepted answer a better suited solution because it plays well with hoisting as argument injection invocation becomes part of constructor declaration and not as a decorator like in your case. But as initially pointed out. I still find your solution (with minor modifications) plausible. – Robert Koritnik Jan 09 '15 at 03:58
  • @Bergi Your link is as wrong as you are. What if - for debugging purposes - we got the callsite of our constructor function, and read the exact code line there if callsite information is not enough to determine if `new` is present? This is obviously not optimal for performance, neither clean or elegant maybe, but wouldn't it be possible? – laconbass Jan 09 '15 at 04:05
  • @RobertKoritnik: Yes, if you want hoistable declarations then you need to call your function from the constructor, and explicitly pass the constructor function, the instance, and the arguments (which I found not appealing). For decorating, we want to use a function expression however, because if it was hoisted and only redefined later it might have been used in its wrong form in between. In that case, you'd also have to care about the prototype object [like in here](http://stackoverflow.com/a/21243884/1048572). – Bergi Jan 09 '15 at 13:26
  • @laconbass: Of course you can extend any debugging protocol to include such information about calls, but that information would be available to the debugger and not the executed code itself. I thought about un-evaling your own code as well, but if you want to do this in a foolproof and failsafe way (to *correctly* identify "the exact code line") you'd end up writing a complete javascript interpreter (written in JS), reading all the script files and basically executing your application in a virtual machine - with access to its own debugging information. Possible, yes, but not exactly desirable. – Bergi Jan 09 '15 at 13:36
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)
laconbass
  • 17,080
  • 8
  • 46
  • 54
  • Yes I was aware of the bug that can be resolved as you've done it by providing the context to the function. – Robert Koritnik Jan 08 '15 at 17:08
  • @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. – laconbass Jan 08 '15 at 17:14
  • 2
    Notice that `this.arguments` is deprecated and does not work in strict mode. – Bergi Jan 08 '15 at 18:47
  • @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 – laconbass Jan 08 '15 at 21:22
  • One more concern... How dos this fare with javascript minification as these members are being dynamically injected? – Robert Koritnik Jan 09 '15 at 01:50
  • @RobertKoritnik will work as expected, js minification will not change the argument names of a function declaration. Give it a try `sudo npm install -g jsmin && cat testSO.js | jsmin | node` (being testSO.js a file containing the code I posted after "WARN...") – laconbass Jan 09 '15 at 02:48
  • 1
    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. – Robert Koritnik Jan 09 '15 at 12:32