[翻訳] JavaScriptコンストラクターの問題とそれを解決する3つの方法

はじめに


ご存知のように、次の形式のコンストラクター関数を使用して、JavaScriptで新しいオブジェクトを作成できます。

function Fubar (foo, bar) { this._foo = foo; this._bar = bar; } var snafu = new Fubar("Situation Normal", "All Fsked Up"); 


newキーワードを使用してコンストラクター関数を呼び出すと、新しいオブジェクトが取得され、そのコンストラクターのコンテキストはオブジェクト自体に設定されます。 コンストラクターから明示的に何も返さない場合、結果としてオブジェクト自体を取得します。 したがって、コンストラクター関数の本体は新しく作成されたオブジェクトを初期化するために使用され、そのプロトタイプはコンストラクターのprototypeプロパティのコンテンツになるため、次のように記述できます。

 Fubar.prototype.concatenated = function () { return this._foo + " " + this._bar; } snafu.concatenated() //=> 'Situation Normal All Fsked Up' 


instanceof演算子を使用すると、特定のコンストラクターを使用してオブジェクトが作成されたことを確認できます。

 snafu instanceof Fubar //=> true 


(より高度なイディオムの場合、またはプログラミング言語の例外をキャッチし、インタビューで求職者を拷問することで楽しむ有害なトロールの場合、 instanceof 「誤って」作成することは可能です。

問題


誤ってnewキーワードを忘れてコンストラクターを呼び出すとどうなりますか?

 var fubar = Fubar("Fsked Up", "Beyond All Recognition"); fubar //=> undefined 


チャールズ・ジグムント・フアン!? 何も返さない通常の関数を呼び出したため、 fubarは未定義になります。 さらに悪いことに、これは必要なものではありません。

 _foo //=> 'Fsked Up' 


JavaScriptは、コンテキストをグローバルスコープに設定して、通常の関数を実行するので、そこにたどり着きました。 まあ、これは何らかの形で修正することができます:

 function Fubar (foo, bar) { "use strict" this._foo = foo; this._bar = bar; } Fubar("Situation Normal", "All Fsked Up"); //=> TypeError: Cannot set property '_foo' of undefined 


「use strict」の使用はコードや本ではしばしば省略されますが、実稼働では、上記のような場合のために、その使用はほとんど必須と呼ばれます。 ただし、 newキーワードなしで自分自身を呼び出す機能を提供しないコンストラクターは、潜在的な問題です。

では、これで何ができるでしょうか?

ソリューションNo. 1-車の継承


David Hermanは、彼の著書Effective JavaScriptで自動継承について説明していますnewでコンストラクターを呼び出すと、この疑似変数は、いわゆる「クラス」の新しいインスタンスを指します。 これを使用して、コンストラクターがnewコードワードを使用して呼び出されたかどうかを判別できます。

 function Fubar (foo, bar) { "use strict" var obj, ret; if (this instanceof Fubar) { this._foo = foo; this._bar = bar; } else return new Fubar(foo, bar); } Fubar("Situation Normal", "All Fsked Up"); //=> { _foo: 'Situation Normal', _bar: 'All Fsked Up' } 


なぜnewなしで動作させるのですか? このアプローチが解決する問題の1つは、 new Fubar(...)を呼び出せないことnew Fubar(...) 。 例を考えてみましょう:

 function logsArguments (fn) { return function () { console.log.apply(this, arguments); return fn.apply(this, arguments) } } function sum2 (a, b) { return a + b; } var logsSum = logsArguments(sum2); logsSum(2, 2) //=> 2 2 4 


logsArguments 、引数を記録する関数をlogsArguments 、呼び出しの結果を返します。 Fubarでも同じことを試してみましょう。

 function Fubar (foo, bar) { this._foo = foo; this._bar = bar; } Fubar.prototype.concatenated = function () { return this._foo + " " + this._bar; } var LoggingFubar = logsArguments(Fubar); var snafu = new LoggingFubar("Situation Normal", "All Fsked Up"); //=> Situation Normal All Fsked Up snafu.concatenated() //=> TypeError: Object [object Object] has no method 'concatenated' 


snafuFubarではなくLoggingFubarインスタンスであるため、これは機能しません。 しかし、 Fubar自動継承を使用する場合:

 function Fubar (foo, bar) { "use strict" var obj, ret; if (this instanceof Fubar) { this._foo = foo; this._bar = bar; } else { obj = new Fubar(); ret = Fubar.apply(obj, arguments); return ret === undefined ? obj : ret; } } Fubar.prototype.concatenated = function () { return this._foo + " " + this._bar; } var LoggingFubar = logsArguments(Fubar); var snafu = new LoggingFubar("Situation Normal", "All Fsked Up"); //=> Situation Normal All Fsked Up snafu.concatenated() //=> 'Situation Normal All Fsked Up' 


もちろん、 snafuは動作しますが、もちろん、 snafuLoggingFubarではなくFubarインスタンスです。 これが我々が求めていたものであるかどうかを確実に言うことは不可能です。 このメソッドは、「うまく機能している」とは言えないように、リークのない便利な抽象化以上に呼び出すことはできませんが、他のアプローチでは実装がはるかに困難なことが可能になります。

解決策2-オーバーロードされた関数を使用する


オブジェクトが特定のクラスのインスタンスであるかどうかをチェックする関数は非常に便利です。 1つの関数が2つの異なることを実行できるという考えを恐れていない場合は、コンストラクターに独自のinstanceofチェックさせることができinstanceof

 function Fubar (foo, bar) { "use strict" if (this instanceof Fubar) { this._foo = foo; this._bar = bar; } else return arguments[0] instanceof Fubar; } var snafu = new Fubar("Situation Normal", "All Fsked Up"); snafu //=> { _foo: 'Situation Normal', _bar: 'All Fsked Up' } Fubar({}) //=> false Fubar(snafu) //=> true 


これにより、コンストラクターをフィルターとして使用できます。

 var arrayOfSevereProblems = problems.filter(Fubar); 


解決策3-火で燃え尽きる


自動継承の緊急の必要性がなく、何らかの理由でオーバーロードされた関数の使用が機能しない場合でも、 newキーワードを使用せずに誤ってコンストラクターを呼び出さないようにする方法が必要な場合があります。 聞かせて
"use strict"は役立ちますが、万能薬でもありません。 このモードでは、グローバルスコープに値を書き込もうとしてもエラーは生成されません。また、上記の値を書き込む前に何かを行おうとすると、何が起きてもエラーが発生します。

おそらく、自分の手で問題を取り上げる方がはるかに良いでしょうか? Oliver Scherrer 次のソリューションを提供します。

 function Fubar (foo, bar) { "use strict" if (!(this instanceof Fubar)) { throw new Error("Fubar needs to be called with the new keyword"); } this._foo = foo; this._bar = bar; } Fubar("Situation Normal", "All Fsked Up"); //=> Error: Fubar needs to be called with the new keyword 


"use strict"頼るよりも簡単で安全です。 独自のinstanceofを確認する必要がある場合は、コンストラクターで関数メソッドとしてラップできinstanceof

 Fubar.is = function (obj) { return obj instanceof Fubar; } var arrayOfSevereProblems = problems.filter(Fubar.is); 


おわりに


newキーワードなしで呼び出されたコンストラクターは、潜在的な脅威になる可能性があります。 これを回避するには、3つの方法があります。自動継承、オーバーロードされた関数の使用、不正な呼び出しの場合の強制的なエラーのスローです。

著者のオリジナル記事はこちらにあります

この記事にはredditに関する議論が含まれています。

Source: https://habr.com/ru/post/J237959/


All Articles