118

与えられた関数:

function x(arg) { return 30; }

次の 2 つの方法で呼び出すことができます。

result = x(4);
result = new x(4);

1 つ目は 30 を返し、2 つ目はオブジェクトを返します。

関数自体の中で関数が呼び出された方法をどのように検出できますか?

ソリューションが何であれ、次の呼び出しでも機能する必要があります。

var Z = new x(); 
Z.lolol = x; 
Z.lolol();

現在、すべてのソリューションZ.lolol()は、コンストラクターとして呼び出していると考えています。

4

23 に答える 23

95

注: これは ES2015 以降で可能になりました。Daniel Weiner の回答を参照してください。

[ES2015より前に]あなたが望むことが可能だとは思いません。信頼できる推論を行うには、関数内で利用できる十分な情報がありません。

ECMAScript 3rd エディションの仕様を見ると、new x()が呼び出されたときに実行される手順は基本的に次のとおりです。

  • 新しいオブジェクトを作成する
  • 内部の [[Prototype]] プロパティをプロトタイプ プロパティに割り当てます。x
  • 通常どおり呼び出しx、新しいオブジェクトを次のように渡します。this
  • への呼び出しがxオブジェクトを返した場合はそれを返し、それ以外の場合は新しいオブジェクトを返します

関数がどのように呼び出されたかについては、実行中のコードで利用できるものは何もないため、内部でテストできるのxthis値だけです。これは、ここでのすべての回答が行っていることです。x見てきたように、コンストラクターとして呼び出したときの * の新しいインスタンスは、関数として呼び出したときのように渡されたx既存のインスタンスと区別できません。ただし、構築時に作成されたすべての新しいオブジェクトにプロパティを割り当てない限り、次のようになります。xthisxx

function x(y) {
    var isConstructor = false;
    if (this instanceof x // <- You could use arguments.callee instead of x here,
                          // except in in EcmaScript 5 strict mode.
            && !this.__previouslyConstructedByX) {
        isConstructor = true;
        this.__previouslyConstructedByX = true;
    }
    alert(isConstructor);
}

明らかにこれは理想的ではありません。これは、構築されたすべてのオブジェクトに余分な無駄なプロパティが追加されx、上書きされる可能性があるためですが、これが最善の方法だと思います。

(*)x 「のインスタンス」は不正確な用語ですが、十分に近く、「コンストラクターとして呼び出すことによって作成されたオブジェクト」よりも簡潔です。

于 2009-12-10T12:38:33.173 に答える
55

1) 以下を確認できますthis.constructor

function x(y)
{
    if (this.constructor == x)
        alert('called with new');
    else
         alert('called as function');
}

2)はい、戻り値はnewコンテキストで使用されたときに破棄されます

于 2008-12-15T08:48:55.033 に答える
19

注: この回答は、javascript が1999 年からES3にまだあった2008 年に書かれました。それ以来、多くの新機能が追加されたため、より優れたソリューションが存在します。この回答は、歴史的な理由から保持されています。

以下のコードの利点は、関数の名前を 2 回指定する必要がなく、無名関数でも機能することです。

function x() {
    if ( (this instanceof arguments.callee) ) {
      alert("called as constructor");
    } else {
      alert("called as function");
    }
}

更新以下のコメントでclaudiu が指摘しているように、コンストラクターを作成した同じオブジェクトに割り当てると、上記のコードは機能しません。私はそれを行うコードを書いたことは一度もありません。

クラウディウスの例:

var Z = new x();
Z.lolol = x;
Z.lolol();

オブジェクトにプロパティを追加することで、オブジェクトが初期化されているかどうかを検出できます。

function x() {
    if ( (this instanceof arguments.callee && !this.hasOwnProperty("__ClaudiusCornerCase")) ) {
        this.__ClaudiusCornerCase=1;
        alert("called as constructor");
    } else {
        alert("called as function");
    }
}

追加したプロパティを削除すると、上記のコードでも壊れます。ただし、 を含む任意の値で上書きできますが、undefinedそれでも機能します。しかし、削除すると壊れます。

現時点では、関数がコンストラクターとして呼び出されたかどうかを検出するための ecmascript のネイティブ サポートはありません。これは私がこれまでに思いついた最も近いものであり、プロパティを削除しない限り機能するはずです。

于 2008-12-22T18:00:51.747 に答える
8

ボンネットの下では本質的に同じ2つの方法。スコープが何であるかthisをテストしたり、何であるかをテストしたりできますthis.constructor

メソッドをコンストラクターとしてthis呼び出した場合はクラスの新しいインスタンスになり、メソッドをメソッドとして呼び出した場合thisはメソッドのコンテキスト オブジェクトになります。同様に、オブジェクトのコンストラクターは、new として呼び出された場合はメソッド自体になり、それ以外の場合はシステムの Object コンストラクターになります。それは泥のように明らかですが、これは役立つはずです:

var a = {};

a.foo = function () 
{
  if(this==a) //'a' because the context of foo is the parent 'a'
  {
    //method call
  }
  else
  {
    //constructor call
  }
}

var bar = function () 
{
  if(this==window) //and 'window' is the default context here
  {
    //method call
  }
  else
  {
    //constructor call
  }
}

a.baz = function ()
{
  if(this.constructor==a.baz); //or whatever chain you need to reference this method
  {
    //constructor call
  }
  else
  {
    //method call
  }
}
于 2008-12-15T09:12:08.217 に答える
5

コンストラクタ内の [this] のインスタンス タイプを確認するのが方法です。問題は、これ以上面倒なことをしなくても、この方法ではエラーが発生しやすいことです。ただし、解決策があります。

関数 ClassA() を扱っているとしましょう。基本的なアプローチは次のとおりです。

    function ClassA() {
        if (this instanceof arguments.callee) {
            console.log("called as a constructor");
        } else {
            console.log("called as a function");
        }
    }

上記の解決策が期待どおりに機能しない場合、いくつかの原因が考えられます。次の 2 つだけを考えてみましょう。

    var instance = new ClassA;
    instance.classAFunction = ClassA;
    instance.classAFunction(); // <-- this will appear as constructor call

    ClassA.apply(instance); //<-- this too

これらを克服するために、a) インスタンスのフィールドに「ConstructorFinished」などの情報を配置して再度確認するか、b) 構築されたオブジェクトをリストで追跡することを提案する人もいます。ClassA のすべてのインスタンスを変更することは、型に関連する機能が機能するにはあまりにも侵襲的で費用がかかるため、私は両方に不快感を覚えます。ClassA に多くのインスタンスがある場合、リスト内のすべてのオブジェクトを収集すると、ガベージ コレクションとリソースの問題が発生する可能性があります。

進むべき道は、ClassA 関数の実行を制御できるようにすることです。簡単なアプローチは次のとおりです。

    function createConstructor(typeFunction) {
        return typeFunction.bind({});
    }

    var ClassA = createConstructor(
        function ClassA() {
            if (this instanceof arguments.callee) {
                console.log("called as a function");
                return;
            }
            console.log("called as a constructor");
        });

    var instance = new ClassA();

これにより、[this] 値を使ったトリックの試みを効果的に防止できます。バインドされた関数は、 new演算子を使用して呼び出さない限り、元の [this] コンテキストを常に保持します。

高度なバージョンでは、コンストラクターを任意のオブジェクトに適用する機能が返されます。コンストラクターを型コンバーターとして使用したり、継承のシナリオで基本クラス コンストラクターの呼び出し可能なチェーンを提供したりできます。

    function createConstructor(typeFunction) {
        var result = typeFunction.bind({});
        result.apply = function (ths, args) {
            try {
                typeFunction.inApplyMode = true;
                typeFunction.apply(ths, args);
            } finally {
                delete typeFunction.inApplyMode;
            }
        };
        return result;
    }

    var ClassA = createConstructor(
        function ClassA() {
            if (this instanceof arguments.callee && !arguments.callee.inApplyMode) {
                console.log("called as a constructor");
            } else {
                console.log("called as a function");
            }
        });
于 2012-03-14T20:18:01.723 に答える
3

Gregs ソリューションを拡張すると、これは提供したテスト ケースで完全に機能します。

function x(y) {
    if( this.constructor == arguments.callee && !this._constructed ) {
        this._constructed = true;
        alert('called with new');
    } else {
        alert('called as function');
    }
}

編集:いくつかのテストケースを追加

x(4);             // OK, function
var X = new x(4); // OK, new

var Z = new x();  // OK, new
Z.lolol = x; 
Z.lolol();        // OK, function

var Y = x;
Y();              // OK, function
var y = new Y();  // OK, new
y.lolol = Y;
y.lolol();        // OK, function
于 2009-12-13T15:52:42.457 に答える
3

関数が JavaScript コードでどのように呼び出されるかを区別する信頼できる方法はありません。1

ただし、関数呼び出しはthisグローバル オブジェクトにthis割り当てられ、コンストラクターは新しいオブジェクトに割り当てられます。この新しいオブジェクトをグローバル オブジェクトにすることはできません。実装によってグローバル オブジェクトを設定できるようになったとしても、それを行う機会がまだないからです。

を返す関数 (heh) として呼び出される関数を持つことで、グローバル オブジェクトを取得できますthis

私の直感では、ECMAScript 1.3 の仕様では、関数として呼び出されたときの動作が定義されているコンストラクターは、この比較を使用して呼び出された方法を区別することになっています。

function MyClass () {
    if ( this === (function () { return this; })() ) {
        // called as a function
    }
    else {
        // called as a constructor
    }
}

とにかく、誰でも関数またはコンストラクターのcallorapplyを使用して、何にでも設定thisできます。しかし、このようにして、グローバル オブジェクトの「初期化」を回避できます。

function MyClass () {
    if ( this === (function () { return this; })() ) {
        // Maybe the caller forgot the "new" keyword
        return new MyClass();
    }
    else {
        // initialize
    }
}

1.[[Call]]ホスト (別名実装) は、内部プロパティと同等のものを実装している場合、違いを見分けることができる場合があり[[Construct]]ます。前者は関数またはメソッドの式に対して呼び出され、後者はnew式に対して呼び出されます。

于 2012-03-12T11:19:01.690 に答える
3

このスレッドを見るまで、コンストラクターがインスタンスのプロパティであるとは考えもしませんでしたが、次のコードはそのまれなシナリオをカバーしていると思います。

// Store instances in a variable to compare against the current this
// Based on Tim Down's solution where instances are tracked
var Klass = (function () {
    // Store references to each instance in a "class"-level closure
    var instances = [];

    // The actual constructor function
    return function () {
        if (this instanceof Klass && instances.indexOf(this) === -1) {
            instances.push(this);
            console.log("constructor");
        } else {
            console.log("not constructor");
        }
    };
}());

var instance = new Klass();  // "constructor"
instance.klass = Klass;
instance.klass();            // "not constructor"

ほとんどの場合、instanceof を確認するだけです。

于 2010-09-15T22:38:40.357 に答える
2

ジョン・レシグ より:

function makecls() {

   return function(args) {

        if( this instanceof arguments.callee) {
            if ( typeof this.init == "function")
                this.init.apply(this, args.callee ? args : arguments)
        }else{
            return new arguments.callee(args);
        }
    };
}

var User = makecls();

User.prototype.init = function(first, last){

    this.name = first + last;
};

var user = User("John", "Resig");

user.name
于 2010-09-15T20:02:11.950 に答える
2

http://packagesinjavascript.wordpress.com/のテストで、テスト if (this == window) がすべての場合にクロスブラウザーで動作することがわかったので、最終的にそれを使用しました。

-ステイン

于 2009-08-06T13:28:58.330 に答える
2

ハックする場合は、他の回答と同様にinstanceof、最小の解決策です。new.targetただし、instanceofソリューションを使用すると、次の例では失敗します。

let inst = new x;
x.call(inst);

@TimDown ソリューションと組み合わせてWeakSet、古い ECMAScript バージョンとの互換性が必要な場合は ES6 を使用して、インスタンス内にプロパティを配置しないようにすることができます。まあ、WeakSet未使用のオブジェクトをガベージコレクションできるようにするために使用されます。new.targetこれは ES6 の構文機能であるため、同じソース コードでは互換性がありません。ECMAScript では、識別子を予約語にすることnewはできず、オブジェクトではないことを指定しています。

(function factory()
{
    'use strict';
    var log = console.log;

    function x()
    {
        log(isConstructing(this) ?
            'Constructing' :
            'Not constructing'
        );
    }

    var isConstructing, tracks;
    var hasOwnProperty = {}.hasOwnProperty;

    if (typeof WeakMap === 'function')
    {
        tracks = new WeakSet;
        isConstructing = function(inst)
        {
            if (inst instanceof x)
            {
                return tracks.has(inst) ?
                    false : !!tracks.add(inst);
            }
            return false;
        }
    } else {
        isConstructing = function(inst)
        {
            return inst._constructed ?
                false : inst._constructed = true;
        };
    }
    var z = new x; // Constructing
    x.call(z)      // Not constructing
})();

ECMAScript 3 のinstanceofoperator of は次のように指定されます。

11.8.6 instanceof 演算子
--- 製品 RelationalExpression: RelationalExpression ShiftExpressionのインスタンス
は、次のように評価されます。 --- 1. RelationalExpression を評価します。
--- 2. GetValue(Result(1)) を呼び出します。
--- 3. ShiftExpression を評価します。
--- 4. GetValue(Result(3)) を呼び出します。
--- 5. Result(4) がオブジェクトでない場合、TypeError例外をスローします。
--- 6. Result(4) に [[HasInstance]] メソッドがない場合、TypeError例外をスローします。
--- 7. パラメータ Result(2) を指定して Result(4) の [[HasInstance]] メソッドを呼び出します。
--- 8. Result(7) を返します。
15.3.5.3 [[HasInstance]] (V)
--- F が関数オブジェクトであると仮定します。
--- F の [[HasInstance]] メソッドが値 V で呼び出されると、次の手順が実行されます:
--- 1. V がオブジェクトでない場合は、falseを返します。
--- 2. F の [[Get]] メソッドをプロパティ名"prototype"で呼び出します。
--- 3. O を Result(2) とします。
--- 4. O がオブジェクトでない場合、TypeError例外をスローします。
--- 5. V を V の [[Prototype]] プロパティの値とする。
--- 6. V が **null** の場合、falseを返す。
--- 7. O と V が同じオブジェクトを参照する場合、または互いに結合されたオブジェクトを参照する場合 (13.1.2)、trueを返します。
--- 8. 手順 5 に進みます。

つまり、プロトタイプに移動した後、オブジェクトでなくなるか、指定されたメソッドで右側のオブジェクトのプロトタイプと等しくなるまで、左側の値を再帰します[[HasInstance]]。つまり、左側が右側のインスタンスであるかどうかをチェックし、左側のすべての内部プロトタイプを消費します。

function x() {
    if (this instanceof x) {
        /* Probably invoked as constructor */
    } else return 30;
}
于 2016-07-28T10:48:34.473 に答える
1

多分私は間違っているかもしれませんが、(パラサイトを犠牲にして)次のコードは解決策のようです:

function x(arg) {
    //console.debug('_' in this ? 'function' : 'constructor'); //WRONG!!!
    //
    // RIGHT(as accepted)
    console.debug((this instanceof x && !('_' in this)) ? 'function' : 'constructor');
    this._ = 1;
    return 30;
}
var result1 = x(4),     // function
    result2 = new x(4), // constructor
    Z = new x();        // constructor
Z.lolol = x; 
Z.lolol();              // function
于 2013-11-15T15:32:48.987 に答える
1

解決策は、コンストラクター関数を実際のコンストラクター関数と、必要に応じてそのプロトタイプ コンストラクターのラッパーに変えることだと思います。この方法は 2009 年から ES5 で機能し、strict モードでも機能します。以下のコード ウィンドウでは、モジュール パターンを使用して、実際のコンストラクターとそのプロトタイプのコンストラクターをクロージャーに保持する例を示しています。これは、コンストラクター (ラッパー)内のスコープを介してアクセスできます。これが機能するのは、Constructor(wrapper) 内の「this」キーワードにプロパティが追加されておらず、Constructor(wrapper).prototype が設定されておらず、デフォルトでObjectも設定されていないためです。したがって、Object.getpropertyNamesから返される配列new キーワードが Constructor(wrapper) で使用されている場合、長さは 0 になります。true の場合、新しい Vector を返します。

var Vector = (function() {
        
     var Vector__proto__ = function Vector() {
         // Vector methods go here
     }
            
     var vector__proto__ = new Vector__proto__();;
        
     var Vector = function(size) {
         // vector properties and values go here
         this.x = 0;
         this.y = 0;
         this.x = 0;
         this.maxLen = size === undefined? -1 : size;
                
     };
     Vector.prototype = vector__proto__;
        
     return function(size){
                
         if ( Object.getOwnPropertyNames(this).length === 0 ) {
             // the new keyword WAS USED with the wrapper constructor
             return new Vector(size); 
         } else { 
             // the new keyword was NOT USED with the wrapper constructor
             return; 
         };
    };
})();
于 2021-04-01T14:04:31.327 に答える
0

(オプションで、パフォーマンスを向上させる関数にthis instanceof arguments.callee置き換える)を使用して、何かがコンストラクターとして呼び出されているかどうかを確認します。簡単に交換できるので使用しないでください。arguments.calleethis.constructor

于 2009-12-09T20:44:22.453 に答える
0
function createConstructor(func) {
    return func.bind(Object.create(null));
}

var myClass = createConstructor(function myClass() {
    if (this instanceof myClass) {
        console.log('You used the "new" keyword');
    } else {
        console.log('You did NOT use the "new" keyword');
        return;
    }
    // constructor logic here
    // ...
});
于 2015-07-17T20:19:19.140 に答える
0

オブジェクトにプロパティを入れたくない場合__previouslyConstructedByX- オブジェクトのパブリック インターフェイスを汚染し、簡単に上書きされる可能性があるため - のインスタンスを返さないでくださいx:

function x() {

    if(this instanceof x) {
        console.log("You invoked the new keyword!");
        return that;
    }
    else {
        console.log("No new keyword");
        return undefined;
    }

}

x();
var Z = new x(); 
Z.lolol = x; 
Z.lolol();
new Z.lolol();

現在、x関数は type のオブジェクトを返すことはないxため、(私が思うに)関数がキーワードthis instanceof xで呼び出された場合にのみ true と評価されます。new

欠点は、これが の動作を効果的に台無しinstanceofにすることですが、それをどれだけ使用するか (私はそうする傾向はありません) によっては、問題にならない場合があります。


両方のケースで を返すことが目標である場合は、 のインスタンスではなく の30インスタンスを返すことができます。Numberx

function x() {

    if(this instanceof x) {
        console.log("You invoked the new keyword!");
        var that = {};
        return new Number(30);
    }
    else {
        console.log("No new");
        return 30;
    }

}

console.log(x());
var Z = new x();
console.log(Z);
Z.lolol = x;
console.log(Z.lolol());
console.log(new Z.lolol());
于 2012-09-11T05:28:01.047 に答える
0

ティム・ダウンは正しいと思います。2 つの呼び出しモードを区別できるようにする必要があると考えるところまで来たら、" this" キーワードを使用しないでください。this信頼性が低く、グローバル オブジェクトである可能性もあれば、まったく異なるオブジェクトである可能性もあります。実際には、これらの異なるアクティベーション モードを備えた機能を持つことは、意図したとおりに機能するものもあれば、完全にワイルドな動作をするものもあるということであり、望ましくありません。多分あなたはそのためにこれを理解しようとしていると思います。

どのように呼び出されても同じように動作するコンストラクター関数を作成する慣用的な方法があります。Thing()、new Thing()、または foo.Thing() のようなものかどうか。こんなふうになります:

function Thing () {
   var that = Object.create(Thing.prototype);
   that.foo="bar";
   that.bar="baz";
   return that;
}

Object.create は新しい ecmascript 5 標準メソッドで、次のように通常の JavaScript で実装できます。

if(!Object.create) {
    Object.create = function(Function){
        // WebReflection Revision
       return function(Object){
           Function.prototype = Object;
           return new Function;
    }}(function(){});
}

Object.create はオブジェクトをパラメーターとして取り、渡されたオブジェクトをプロトタイプとして新しいオブジェクトを返します。

ただし、関数の呼び出し方法に応じて関数の動作を変えようとしているのであれば、あなたは悪い人であり、JavaScript コードを書くべきではありません。

于 2009-12-10T13:41:00.100 に答える
0

質問に加えて、以下のコードは、関数が new なしで呼び出された場合に問題を自動修正します。

function Car() {

    if (!(this instanceof Car)) return new Car();

    this.a = 1;
    console.log("Called as Constructor");

}
let c1 = new Car();
console.log(c1);
于 2019-02-15T16:33:33.200 に答える