はじめに
ご存知のように、次の形式のコンストラクター関数を使用して、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()
instanceof
演算子を使用すると、特定のコンストラクターを使用してオブジェクトが作成されたことを確認できます。
snafu instanceof Fubar
(より高度なイディオムの場合、またはプログラミング言語の例外をキャッチし、インタビューで求職者を拷問することで楽しむ有害なトロールの場合、
instanceof
「誤って」作成することは可能です。
問題
誤って
new
キーワードを忘れてコンストラクターを呼び出すとどうなりますか?
var fubar = Fubar("Fsked Up", "Beyond All Recognition"); fubar
チャールズ・ジグムント・フアン!? 何も返さない通常の関数を呼び出したため、
fubar
は未定義になります。 さらに悪いことに、これは必要なものではありません。
_foo
JavaScriptは、コンテキストをグローバルスコープに設定して、通常の関数を実行するので、そこにたどり着きました。 まあ、これは何らかの形で修正することができます:
function Fubar (foo, bar) { "use strict" this._foo = foo; this._bar = bar; } Fubar("Situation Normal", "All Fsked Up");
「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");
なぜ
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)
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");
snafu
は
Fubar
ではなく
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");
もちろん、
snafu
は動作しますが、もちろん、
snafu
は
LoggingFubar
ではなく
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
これにより、コンストラクターをフィルターとして使用できます。
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");
"use strict"
頼るよりも簡単で安全です。 独自の
instanceof
を確認する必要がある場合は、コンストラクターで関数メソッドとしてラップでき
instanceof
。
Fubar.is = function (obj) { return obj instanceof Fubar; } var arrayOfSevereProblems = problems.filter(Fubar.is);
おわりに
new
キーワードなしで呼び出されたコンストラクターは、潜在的な脅威になる可能性があります。 これを回避するには、3つの方法があります。自動継承、オーバーロードされた関数の使用、不正な呼び出しの場合の強制的なエラーのスローです。
著者のオリジナル記事は
こちらにあります 。
この記事には
redditに関する議論が含まれています。