Advanced [removed] Detecting whether current funct

2019-06-23 19:54发布

问题:

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

回答1:

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)


回答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();