与えられた関数:
_function x(arg) { return 30; }
_
次の2つの方法で呼び出すことができます。
_result = x(4);
result = new x(4);
_
最初は30を返し、2番目はオブジェクトを返します。
どのように関数が呼び出されたかを検出するにはどうすればいいですか?関数自体の内部
ソリューションが何であれ、次の呼び出しでも機能する必要があります。
_var Z = new x();
Z.lolol = x;
Z.lolol();
_
現在、すべてのソリューションは、Z.lolol()
がコンストラクタとしてそれを呼び出していると考えています。
注:これはES2015以降で可能になりました。 ダニエル・ワイナーの答え 。を参照
あなたが望むことは可能だとは思いません[ES2015以前]。信頼できる推論を行うのに十分な情報が関数内にありません。
ECMAScript第3版の仕様を見ると、new x()
が呼び出されたときに実行される手順は基本的に次のとおりです。
x
のprototypeプロパティに割り当てますx
を呼び出し、新しいオブジェクトをthis
として渡しますx
の呼び出しがオブジェクトを返した場合はそれを返し、そうでない場合は新しいオブジェクトを返します関数の呼び出し方法に関する有用なものは実行中のコードで利用できないため、x
内でテストできるのはthis
値のみです。これがここでのすべての答えです。 。ご覧のように、コンストラクターとしてx
を呼び出したときに、* x
の新しいインスタンスは、x
として渡されたthis
の既存のインスタンスと区別できません。 _ x
を関数として呼び出すとき、unlessx
によって作成されたすべての新しいオブジェクトにプロパティを割り当てます構築されます:
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
によって構築されたすべてのオブジェクトに余分な無用のプロパティがあり、それが上書きされる可能性があるからです。
(*)「instance of」は不正確な用語ですが、十分に近く、「x
コンストラクターとして」
ECMAScript 6以降、これは new.target
。 new.target
は、関数がnew
(またはReflect.construct
、new
)のように機能します。それ以外の場合は、undefined
です。
function Foo() {
if (new.target) {
console.log('called with new');
} else {
console.log('not called with new');
}
}
new Foo(); // "called with new"
Foo(); // "not called with new"
Foo.call({}); // "not called with new"
1)this.constructor
:
function x(y)
{
if (this.constructor == x)
alert('called with new');
else
alert('called as function');
}
2)はい、new
コンテキストで使用された場合、戻り値は破棄されます
注:この答えは、2008に書かれていましたが、javascriptはES3から1999。それ以来、多くの新しい機能が追加されているため、より良いソリューションが存在します。この答えは歴史的な理由で保持されています。
以下のコードの利点は、関数の名前を2回指定する必要がなく、匿名関数でも機能することです。
function x() {
if ( (this instanceof arguments.callee) ) {
alert("called as constructor");
} else {
alert("called as function");
}
}
Update下のコメントで claudi が指摘しているように、コンストラクターを作成した同じオブジェクトにコンストラクターを割り当てると、上記のコードは機能しません。私はそれを行うコードを書いたことがなく、他の誰かがそれを行うのを見たことがあります。
クラウディウスの例:
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のネイティブサポートはありません。これは私がこれまでに考えた中で最も近いものであり、プロパティを削除しない限り機能するはずです。
基本的に同じ内部の2つの方法。 this
のスコープをテストするか、this.constructor
です。
メソッドをコンストラクターとして呼び出した場合、this
はクラスの新しいインスタンスになり、メソッドとしてメソッドを呼び出した場合、this
はメソッドのコンテキストオブジェクトになります。同様に、オブジェクトのコンストラクターは、新規として呼び出された場合はメソッド自体になり、そうでない場合はシステムオブジェクトコンストラクターになります。それは泥として明らかですが、これは役立つはずです:
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
}
}
コンストラクタ内で[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");
}
});
実際には、解決策は非常に可能で簡単です...なぜそんなに多くの言葉がこんなに小さなもののために書かれているのか理解できない
UPDATE:TwilightSunテストのClaudiuが提案された場合でも、解決策が完成しました!君たちありがとう!!!
function Something()
{
this.constructed;
if (Something.prototype.isPrototypeOf(this) && !this.constructed)
{
console.log("called as a c'tor"); this.constructed = true;
}
else
{
console.log("called as a function");
}
}
Something(); //"called as a function"
new Something(); //"called as a c'tor"
ここに示されています: https://jsfiddle.net/9cqtppuf/
JavaScriptコードで関数がどのように呼び出されるかを区別する信頼できる方法はありません。1
ただし、関数呼び出しではthis
がグローバルオブジェクトに割り当てられ、コンストラクターではthis
が新しいオブジェクトに割り当てられます。この新しいオブジェクトをグローバルオブジェクトにすることはできません。実装でグローバルオブジェクトを設定できる場合でも、それを行う機会がまだないためです。
グローバルオブジェクトを取得するには、this
を返す関数(heh)として呼び出される関数を使用します。
私の直感では、ECMAScript 1.3の仕様では、関数として呼び出されたときの動作が定義されているコンストラクターは、この比較を使用して呼び出された方法を区別することになっています。
function MyClass () {
if ( this === (function () { return this; })() ) {
// called as a function
}
else {
// called as a constructor
}
}
とにかく、誰でも関数またはコンストラクタのcall
またはapply
を使用して、this
を任意に設定できます。ただし、この方法では、グローバルオブジェクトの「初期化」を回避できます。
function MyClass () {
if ( this === (function () { return this; })() ) {
// Maybe the caller forgot the "new" keyword
return new MyClass();
}
else {
// initialize
}
}
1。 ホスト(実装とも呼ばれます)は、内部プロパティ[[Call]]
および[[Construct]]
。前者は関数式またはメソッド式に対して呼び出され、後者はnew
式に対して呼び出されます。
このスレッドを見るまで、コンストラクタがインスタンスのプロパティであるとは考えませんでしたが、次のコードはそのまれなシナリオをカバーしていると思います。
// 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をチェックするだけです。
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
http://packagesinjavascript.wordpress.com/ のテストで、すべての場合で(この==ウィンドウ)がクロスブラウザで動作するかどうかのテストが見つかりました。 。
-Stijn
ジョン・レジグから:
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
あなたがハックするつもりなら、instanceof
が他の回答と同様にnew.target
の後の最小の解決策です。しかし、instanceof
ソリューションを使用すると、この例では失敗します。
let inst = new x;
x.call(inst);
@TimDownソリューションと組み合わせて、古いECMAScriptバージョンとの互換性が必要な場合、ES6のWeakSet
を使用して、インスタンス内にプロパティを配置できません。さて、WeakSet
は未使用のオブジェクトをガベージコレクションできるようにするために使用されます。 new.target
は、ES6の構文機能であるため、同じソースコードでは互換性がありません。 ECMAScriptでは、識別子は予約語の1つにはできず、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のinstanceof
演算子は 指定 として:
11.8.6 instanceof演算子
---プロダクションRelationalExpression:RelationalExpression instanceof ShiftExpressionは次のように評価されます。
--- 1. RelationalExpressionを評価します。
--- 2. GetValue(Result(1))を呼び出します。
--- 3. ShiftExpressionを評価します。
--- 4. GetValue(Result(3))を呼び出します。
--- 5. Result(4)がオブジェクトでない場合、 TypeError 例外。
--- 6. Result(4)に[[HasInstance]]メソッドがない場合、 TypeError 例外。
--- 7. Result(4)の[[HasInstance]]メソッドをパラメーターResult(2)で呼び出します。
--- 8. Result(7)を返します。
15.3.5.3 [[HasInstance]](V)
--- FがFunctionオブジェクトであると仮定します。
--- Fの[[HasInstance]]メソッドが値Vで呼び出されると、次の手順が実行されます。
--- 1. Vがオブジェクトでない場合、リターン false。
--- 2.プロパティ名でFの[[Get]]メソッドを呼び出す "プロトタイプ"。
--- 3. OをResult(2)とします。
--- 4. Oがオブジェクトでない場合、 TypeError 例外。
--- 5. Vを[[Prototype]]プロパティの値とします。
--- 6. Vが** null **の場合、リターン false。
--- 7. OとVが同じオブジェクトを参照する場合、または互いに結合されたオブジェクトを参照する場合(13.1.2)、 本当。
--- 8.手順5に進みます。
そして、それは、オブジェクトにならないか、指定された[[HasInstance]]
メソッドを持つ右側のオブジェクトのプロトタイプに等しくなるまで、プロトタイプに行った後、左側の値を再帰することを意味します。つまり、左手側が右手側のインスタンスであるかどうかをチェックし、左手側のすべての内部プロトタイプを消費します。
function x() {
if (this instanceof x) {
/* Probably invoked as constructor */
} else return 30;
}
たぶん私は間違っていますが、(寄生虫を犠牲にして)次のコードは解決策のように思えます:
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
つかいます this instanceof arguments.callee
(オプションでarguments.callee
は、それが含まれている関数を使用してパフォーマンスを向上させます)、何かがコンストラクターとして呼び出されるかどうかを確認します。 しない use this.constructor
は簡単に変更できます。
オブジェクトの代わりに文字列を返す関数を実装しようとしたときに、同じ問題が発生しました。
関数の先頭に「this」が存在するかどうかを確認するだけで十分なようです:
function RGB(red, green, blue) {
if (this) {
throw new Error("RGB can't be instantiated");
}
var result = "#";
result += toHex(red);
result += toHex(green);
result += toHex(blue);
function toHex(dec) {
var result = dec.toString(16);
if (result.length < 2) {
result = "0" + result;
}
return result;
}
return result;
}
とにかく、結局、RGB()擬似クラスをrgb()関数に変更することにしました。そのため、インスタンス化を試行しないため、安全性チェックはまったく必要ありません。しかし、それはあなたがやろうとしていることに依存します。
質問の一番上で、以下のコードは、関数が新しい関数なしで呼び出された場合の問題を自動修正します。
function Car() {
if (!(this instanceof Car)) return new Car();
this.a = 1;
console.log("Called as Constructor");
}
let c1 = new Car();
console.log(c1);
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
// ...
});
このスレッドは古くからありますが、ストリクトモード( _'use strict'
_ )で関数のデフォルトのthis
値が以前のようにグローバル/ウィンドウに設定されるのではなく未定義であることを誰も言及していないことに驚いています。したがって、newが使用されていないかどうかを確認するには、_!this
_のfalsey
値をテストするだけです-EG:
_function ctor() { 'use strict';
if (typeof this === 'undefined')
console.log('Function called under strict mode (this == undefined)');
else if (this == (window || global))
console.log('Function called normally (this == window)');
else if (this instanceof ctor)
console.log('Function called with new (this == instance)');
return this;
}
_
その関数をそのままテストすると、関数の先頭にある_'use strict'
_ディレクティブにより、this
値として未定義になります。もちろん、すでに厳格モードがオンになっている場合、_'use strict'
_ディレクティブを削除しても変更されませんが、そうでない場合はthis
値がwindow
またはglobal
に設定されます。 new
を使用して関数を呼び出す場合、this
の値はinstanceofチェックと一致します(他の項目をチェックした場合、インスタンスは最後のオプションなので、このチェックは不要です。とにかくインスタンスを継承したい場合は避けてください)
_function ctor() { 'use strict';
if (!this) return ctor.apply(Object.create(ctor.prototype), arguments);
console.log([this].concat([].slice.call(arguments)));
return this;
}
_
これは、this
値とコンソールに関数に渡す引数を記録し、this
値を返します。 this
の値がfalsey
の場合、Object.create(ctor.prototype)
を使用して新しいインスタンスを作成し、Function.apply()
を使用して、this
と同じインスタンスで同じパラメーターでコンストラクターを再呼び出しします。 this
の値がfalsey
以外の場合、有効なインスタンスと見なされて返されます。
ティムダウン私は正しいと思います。 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は、次のような通常のjavascriptで実装できる新しいecmascript 5標準メソッドです。
if(!Object.create) {
Object.create = function(Function){
// WebReflection Revision
return function(Object){
Function.prototype = Object;
return new Function;
}}(function(){});
}
Object.createはオブジェクトをパラメーターとして受け取り、オブジェクトとして渡された新しいオブジェクトをプロトタイプとして返します。
ただし、関数の呼び出し方法に応じて関数の動作を実際に変えようとしている場合は、あなたは悪人であり、javascriptコードを書くべきではありません。
オブジェクトに__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
関数はx
型のオブジェクトを返さないため、new
キーワードを使用して関数が呼び出された場合にのみthis instanceof x
はtrueと評価されます。
欠点は、これはinstanceof
の動作を効果的に台無しにしますが、使用量に応じて(私はそうは思わないでしょうが)問題ではないかもしれません。
両方のケースで30
を返すことが目標であれば、Number
のインスタンスの代わりにx
のインスタンスを返すことができます。
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());