高级 Javascript:检测构造函数是否正在调用当前函数

Advanced Javascript: Detecting whether current function is being called by a constructor

在 Javascript 函数中,检测函数是简单执行还是作为对象实例构造函数执行(使用 new 关键字)是一个相当简单的检测。

// constructor
function SomeType() {
    if (this instanceof SomeType)
        // called as an object instance constructor
    else
        // the usual function call
}

没关系,这个问题已经在 SO 上至少回答过几次了。

现在假设我们的构造函数调用了我直接在 Function 原型上定义的另一个函数,因此所有函数都可以访问它——这就是我这样做的主要目的。

Function.prototype.doSomething = function doSomething() {
    // what code here?
};

// constructor
function SomeType() {
    SomeType.doSomething();
}

主要问题

我们现在如何在 doSomething 中检测到 SomeType 函数的相同内容?

我想检测它的原因是我正在编写一个采用/注入构造函数参数的函数具有相同名称的对象实例成员。当然,这个函数应该只在被构造函数调用时执行,而不是被定期调用的函数调用。

This is my answer 到另一个问题,您可以在其中看到我的 adoptArguments 函数,该函数将对象构造函数参数作为成员放入构造的对象实例中。

强制执行特定用法的解决方法 = 不好

我有一个可能的解决方法,但我不想使用,因为它强制执行正确的用法 - 执行上下文注入。这是可以检测对象实例构造函数执行的代码:

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);
}

这个想法可能会激发你自己的一些想法来解决原来的问题

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).

其实那是不可能的,一个cannot know在JS中一个用户函数是否作为构造函数被调用。 this instanceof 测试对于通常的情况已经足够了,但只检查上下文是否确实继承自 class 的原型。

How can we now detect within doSomething the same for SomeType function?

出于同样的原因,您不能进行 instanceof 测试而不将 this 作为参数传递给您的 doSomething

Main problem: I'm writing a function that adopts/injects constructor parameters as constructed object instance members with the same name.

我建议不要通过构造函数内部的函数调用来这样做。相反,尝试修饰构造函数,以便您可以立即访问所需的所有值:

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;
};

然后代替

function SomeType() {
    SomeType.adoptArguments();
}

var SomeType = function() {

}.adoptArguments();

一种可能的解决方案是将构造函数上下文作为参数传递。无需传递参数对象,因为它可以通过 this.arguments 访问,就像您在 adoptArguments 链接答案中所做的那样。

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);
}

警告:您的 adoptArguments 函数存在严重错误,见下文

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)