JavaScriptでの新しい演算子の回避-より良い方法


16

警告:これは長い投稿です。

シンプルにしましょう。JavaScriptでコンストラクターを呼び出すたびにnew演算子にプレフィックスを付ける必要はありません。これは、私がそれを忘れがちであり、私のコードがひどく失敗するからです。

これを回避する簡単な方法はこれです...

function Make(x) {
  if ( !(this instanceof arguments.callee) )
  return new arguments.callee(x);

  // do your stuff...
}

しかし、変数noを受け入れるにはこれが必要です。このような引数の...

m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');

最初の即時解決策は、このような「適用」方法のようです...

function Make() {
  if ( !(this instanceof arguments.callee) )
    return new arguments.callee.apply(null, arguments);

  // do your stuff
}

ただし、これは間違っています。新しいオブジェクトはapplyコンストラクタではなくメソッドに渡されますarguments.callee

今、私は3つの解決策を思いつきました。私の簡単な質問は次のとおりです。または、より良い方法がある場合は、それを教えてください。

最初eval()コンストラクターを呼び出すJavaScriptコードを動的に作成するために使用します。

function Make(/* ... */) {
  if ( !(this instanceof arguments.callee) ) {
    // collect all the arguments
    var arr = [];
    for ( var i = 0; arguments[i]; i++ )
      arr.push( 'arguments[' + i + ']' );

    // create code
    var code = 'new arguments.callee(' + arr.join(',') + ');';

    // call it
    return eval( code );
  }

  // do your stuff with variable arguments...
}

2番目 -すべてのオブジェクト__proto__には、そのプロトタイプオブジェクトへの「秘密」リンクであるプロパティがあります。幸いなことに、このプロパティは書き込み可能です。

function Make(/* ... */) {
  var obj = {};

  // do your stuff on 'obj' just like you'd do on 'this'
  // use the variable arguments here

  // now do the __proto__ magic
  // by 'mutating' obj to make it a different object

  obj.__proto__ = arguments.callee.prototype;

  // must return obj
  return obj;
}

3番目 –これは2番目のソリューションに似ています。

function Make(/* ... */) {
  // we'll set '_construct' outside
  var obj = new arguments.callee._construct();

  // now do your stuff on 'obj' just like you'd do on 'this'
  // use the variable arguments here

  // you have to return obj
  return obj;
}

// now first set the _construct property to an empty function
Make._construct = function() {};

// and then mutate the prototype of _construct
Make._construct.prototype = Make.prototype;

  • eval 解決策は不器用で、「悪の評価」のすべての問題が伴います。

  • __proto__ ソリューションは非標準であり、「mIsERYの素晴らしいブラウザ」はそれを尊重しません。

  • 3番目の解決策は過度に複雑に思えます。

しかし、上記の3つのすべてのソリューションを使用すると、このようなことができます。

m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');

m1 instanceof Make; // true
m2 instanceof Make; // true
m3 instanceof Make; // true

Make.prototype.fire = function() {
  // ...
};

m1.fire();
m2.fire();
m3.fire();

事実上、上記のソリューションは変数noを受け入れる「真の」コンストラクターを提供します。の引数を必要としませんnew。これについてどう思いますか。

-更新-

「エラーを投げるだけ」と言っている人もいます。私の回答は次のとおりです。10以上のコンストラクタで重いアプリを実行しているので、すべてのコンストラクタがコンソールにエラーメッセージをスローすることなくその間違いを「スマートに」処理できると、はるかに使いやすくなると思います。


2
または、スタックトレースを調べてコードを修正できる場合は、エラーをスローします
ラチェットフリーク

2
この質問は、Stack OverflowまたはCode Reviewでよりよく聞かれると思います。概念的な質問ではなく、かなりコード中心のようです。
アダムリア

1
@greengitはエラーをスローするのではなく、jslintを使用します。あなたがした場合は警告を表示しますMake()せずにnewメイクが大文字であるので、それはそれはコンストラクタであると仮定しているため
レイノス

1
待ってください-これを達成するためのより良い方法を探していますか、それとも変数引数オブジェクトを作成せずにコードを提供してくれる人を探していますnewか?後者の場合、おそらく間違ったサイトを尋ねているからです。前者の場合、新しいエラーの検出とエラーの検出に関する提案をすぐに却下したくない場合があります。アプリが本当に「重い」場合、最後に望むのは、速度を落とすための過剰な構築メカニズムです。new、それが得るすべてのフラックのために、かなり迅速です。
Shog9

5
皮肉なことに、プログラマのミスを「賢く」処理しようとすること自体が、JavaScriptの「悪い部分」の多くの原因となります。
ダニエルプラット

回答:


19

まずarguments.callee、ES5 strictでは非推奨になっているため、使用しません。実際のソリューションはかなり単純です。

まったく使用newしません。

var Make = function () {
  if (Object.getPrototypeOf(this) !== Make.prototype) {
    var o = Object.create(Make.prototype);
    o.constructor.apply(o, arguments);
    return o;
  }
  ...
}

それはお尻の正しい痛みですか?

試して enhance

var Make = enhance(function () {
  ...
});

var enhance = function (constr) {
  return function () {
    if (Object.getPrototypeOf(this) !== constr.prototype) {
      var o = Object.create(constr.prototype);
      constr.apply(o, arguments);
      return o;
    }
    return constr.apply(this, arguments);
  }
}

もちろん、これにはES5が必要ですが、誰もがES5-shimを使用していますか?

代替のjs OOパターンにも興味があるかもしれません

余談ですが、オプション2を

var Make = function () {
  var that = Object.create(Make.prototype);
  // use that 

  return that;
}

独自のES5 Object.createシムが必要な場合は、本当に簡単です

Object.create = function (proto) {
  var dummy = function () {};
  dummy.prototype = proto;
  return new dummy;
};

はい、本当です-しかし、すべての魔法はここにありObject.createます。ES5以前はどうですか?ES5-ShimはObject.createDUBIOUSとしてリストしています。
ツリーコーダー

@greengitをもう一度読むと、ES5 shimはObject.createの2番目の引数が疑わしいと述べています。最初のものは大丈夫です。
レイノス

1
はい、私はそれを読みました。そして(IF)彼らは__proto__そこにある種のものを使用していると思う、それから我々はまだ同じ点にいる。ES5以前では、プロトタイプを変更する簡単な方法はありません。しかし、とにかく、あなたのソリューションは最もエレガントで前向きです。そのために+1。(私の投票制限に達しました)
ツリーコーダー

1
@psrに感謝します。そして、@ RaynosはあなたのObject.createシムがほとんど私の3番目のソリューションですが、もちろん私のものよりも複雑ではなく、見た目も良いです。
ツリーコーダー

36

シンプルにしましょう。JavaScriptでコンストラクターを呼び出すたびにnew演算子にプレフィックスを付ける必要はありません。これは、私がそれを忘れがちであり、私のコードがひどく失敗するからです。

明らかな答えは、newキーワードを忘れないでください。

言語の構造と意味を変更しています。

私の意見では、そしてあなたのコードの将来のメンテナーのために、これは恐ろしいアイデアです。


9
+1コーディングの悪い習慣を言語にまき散らすのは奇妙に思えます。確かに、いくつかの構文の強調表示/チェックインポリシーは、バグが発生しやすいパターン/起こりそうなタイプミスの回避を強制できます。
Stoive

すべてのコンストラクターが使用するnewかどうかを気にしないライブラリを見つけた場合、より保守性が高いと思います。
ジャック

@Jackですが、バグを見つけるのがずっと難しくて微妙です。;to endステートメントを含めた場合、javascriptが「気にしない」ことによって引き起こされるすべてのバグを見てください。(自動セミコロン挿入)
CaffGeek

@CaffGeek「Javascriptセミコロンを気にしない」は正確ではありません-セミコロンを使用する場合と使用しない場合は微妙に異なる場合があり、そうでない場合もあります。それが問題です。受け入れられた答えに示された解決策はまったく逆です-それは、すべての場合に使用するnewかどうかは意味的に同一です。ありません、この不変が壊れて何も微妙なケースは。それが良い理由であり、あなたがそれを使いたい理由です。
ジャック

14

最も簡単な解決策は、new忘れてしまったことを明らかにするために、エラーを覚えて投げるだけです。

if (Object.getPrototypeOf(this) !== Make.prototype) {
    throw new Error('Remember to call "new"!');
}

何をするにしても、使用しないでくださいeval__proto__これらは非標準であり、機能が変更される可能性があるため、特に非標準のプロパティを使用することは避けます。


.__proto__それは悪魔である反抗的に避ける
Raynos

3

私は実際にこれについて投稿しました。http://js-bits.blogspot.com/2010/08/constructors-without-using-new.html

function Ctor() {
    if (!(this instanceof Ctor) {
        return new Ctor(); 
    }
    // regular construction code
}

さらに、それを一般化して、すべてのコンストラクタの先頭に追加する必要がないようにすることもできます。あなたは私の投稿にアクセスしてそれを見ることができます

免責事項私は自分のコードでこれを使用せず、教訓的な価値のためにのみ投稿しました。を忘れることnewは簡単に発見できるバグであることがわかりました。他の人と同じように、ほとんどのコードで実際にこれが必要だとは思いません。JS継承を作成するためのライブラリを作成している場合を除き、その場合は1つの場所から使用でき、単純な継承とは異なるアプローチを既に使用しています。


潜在的に隠されたバグ:私が持っている場合var x = new Ctor();し、後でとしてのxを持っthisてみませんかvar y = Ctor();予想通り、これは動作しないでしょう。
ルイスキューバル

@luiscubal「後でxを持っている」と言っていることがわからない場合this、潜在的な問題を示すためにjsfiddleを投稿できますか?
フアンメンデス

あなたのコードは私が当初考えていたよりも少し堅牢ですが、(やや複雑ですがまだ有効な)例を思いつくことができました:jsfiddle.net/JHNcR/1
luiscubal

@luiscubalあなたの主張はわかりますが、本当に複雑なものです。あなたは電話しても構いませんCtor.call(ctorInstance, 'value')。私はあなたがしていることの有効なシナリオを見ていません。オブジェクトを構築するには、var x = new Ctor(val)またはを使用しますvar y=Ctor(val)。有効なシナリオがあったとしても、私の主張は、あなたが使用しないでコンストラクタを持つことができるということですnew Ctor、あなたが使用して作業コンストラクタ持つことができないことをCtor.call参照してくださいjsfiddle.net/JHNcR/2を
フアン・メンデス

0

これはどう?

/* thing maker! it makes things! */
function thing(){
    if (!(this instanceof thing)){
        /* call 'new' for the lazy dev who didn't */
        return new thing(arguments, "lazy");
    };

    /* figure out how to use the arguments object, based on the above 'lazy' flag */
    var args = (arguments.length > 0 && arguments[arguments.length - 1] === "lazy") ? arguments[0] : arguments;

    /* set properties as needed */
    this.prop1 = (args.length > 0) ? args[0] : "nope";
    this.prop2 = (args.length > 1) ? args[1] : "nope";
};

/* create 3 new things (mixed 'new' and 'lazy') */
var myThing1 = thing("woo", "hoo");
var myThing2 = new thing("foo", "bar");
var myThing3 = thing();

/* test your new things */
console.log(myThing1.prop1); /* outputs 'woo' */
console.log(myThing1.prop2); /* outputs 'hoo' */

console.log(myThing2.prop1); /* outputs 'foo' */
console.log(myThing2.prop2); /* outputs 'bar' */

console.log(myThing3.prop1); /* outputs 'nope' */
console.log(myThing3.prop2); /* outputs 'nope' */

編集:追加するのを忘れた:

「アプリが本当に「重い」場合、最後に望むのは、速度を落とすための過剰な構築メカニズムです」

私は完全に同意します-「新しい」キーワードなしで「もの」を作成する場合、それはそれよりも遅い/重いです。エラーはあなたの友達です。何が悪いのかを教えてくれるからです。さらに、彼らはあなたの仲間の開発者に彼らが間違っていることを伝えます。


欠落しているコンストラクター引数の値を作成すると、エラーが発生しやすくなります。欠落している場合は、プロパティを未定義のままにします。それらが必須である場合、それらが欠落している場合はエラーをスローします。prop1がboolであると仮定した場合はどうなりますか?真実性について「ノープ」をテストすると、エラーの原因になります。
-JBRウィルキンソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.