【问题标题】:Advanced Javascript: Detecting whether current function is being called by a constructor高级Javascript:检测当前函数是否被构造函数调用
【发布时间】:2015-03-06 11:17:21
【问题描述】:

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

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

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

现在假设我们的构造函数调用了我直接在 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);
}

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

【问题讨论】:

  • hmmm - 如果变通方法需要特定的调用,您还需要强制进行构造检查(而不是调用或应用样式调用)?
  • @Woody:好吧,如果所有子功能都会做这个检查,那就是真的。但不是。在我的实际场景中,这个 doSomething 函数可以做其他事情,并且可以在 this answer I've written 中看到另一个 SO 问题。
  • 所以也许理解为什么你需要知道这两种情况的区别,会导致解决方案。目前还不清楚动机是什么。
  • @MichaelPerrenoud:好的。我编辑了我的主要问题部分来解释我为什么要检测这个...
  • “当然,这个函数应该只在被构造函数调用而不是被定期调用的函数调用时执行。”。您的意思是检测在 any 类型实例的上下文中调用 doSomething 的任何位置,而不仅仅是 SomeType?

标签: javascript oop prototype


【解决方案1】:

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

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

我们现在如何在doSomething 中检测到与SomeType 函数相同的情况?

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

主要问题:我正在编写一个函数,它采用/注入构造函数参数作为具有相同名称的构造对象实例成员。

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

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

【讨论】:

  • 实际上这是非常聪明和合理的,虽然不是用你自己的并且总是使用相同的名称完全覆盖构造函数,而是返回一个由new Function()生成的新函数可能会改变现有的构造函数的主体并保留其他所有内容。 this form 里的东西。你实际上会得到相同的函数,但你会在它的主体中注入额外的语句。 唯一需要解决的问题是对变量的必需赋值。我不确定为什么它实际上是必需的?
  • 实际上,在对象实例化过程中可以避免变量赋值要求,执行var objInstance = new (SomeType.adoptArguments()); 但它似乎和你的方式一样狡猾,尽管我最终得到了原始构造函数名称并更改了函数体。
  • 我的意思是使用 new Function() 来代替你的新构造函数:return new Function(args.join(","), "return function " + funcname + "(" + args.join() + ") {" + args.map(function(name, index){ return "\n\tthis." + name + " = " + name + ";" }).join("") + funcbody + "};")();
  • @Bergi 我的反对意见是因为您的断言 “实际上,这是不可能的,在 JS 中无法知道用户函数是否被调用为构造函数。” 太大胆了。你是对的,this instanceof x 实际上并不能确保函数x 是用new 关键字调用的(只是检查this 继承自x.prototype),但这并不能证明前面的断言。此外,您断言 “您不应该通过构造函数内部的函数调用来这样做。” 没有解释“为什么?”
  • @laconbass:可能是粗体,但它是正确的:用户定义的函数无法区分 ES5 中的 [[call]][[construct]] 调用。另请参阅我现在已链接的the question(请参阅编辑)。 “you should not”是一个建议,因为我的建议不那么复杂(而且恕我直言,其功能风格在语义上更清晰)。
【解决方案2】:

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

这个解决方案对我来说很有意义,因为我希望 Function.prototype.someMethod 是 在 Function 实例的上下文而不是其他上下文中调用 (即新创建的实例)。

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)

【讨论】:

  • 是的,我知道可以通过向函数提供上下文来解决该错误。
  • @RobertKoritnik 我添加了代码来专门回答这个问题,但无论如何我知道它可能无法提供您正在寻找的那种“神奇”检测。
  • 请注意,this.arguments 已被弃用,并且在严格模式下不起作用。
  • @Bergi 你是对的,它的使用可以被绕过为函数提供参数对象,但我离开它是为了让函数尽可能接近 OP 发布的原始内容
  • 其实不是。这在很大程度上取决于缩小服务及其选项。例如,Asp.net Minification and Bundling 使用的重命名函数参数并删除函数体中未使用的参数。当我将thisarguments 传递给我的函数时,所有参数都会从构造函数中删除。这就是我的 Angular 应用程序,我不是在解析函数字符串,而是在我的构造函数上使用 $inject 变量。方便。
猜你喜欢
  • 2010-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多