[解決済み] javascriptで関数をオーバーロードするには?
質問
オーバーロードに対する古典的な(非js)アプローチ。
function myFunc(){
//code
}
function myFunc(overloaded){
//other code
}
Javascriptは同じ名前の関数を複数定義することを許しません。そのため、このようなものが表示されます。
function myFunc(options){
if(options["overloaded"]){
//code
}
}
javascriptの関数のオーバーロードについて、オーバーロードを含むオブジェクトを渡す以外に良い回避策はありますか?
オーバーロードを渡すと、可能性のあるオーバーロードごとに条件文が必要になるため、関数がすぐに冗長になりすぎてしまうことがあります。関数を使用して
//code
を達成するために関数を使用すると、スコープでトリッキーな状況を引き起こす可能性があります。
どのように解決するのか?
Javascriptの引数のオーバーロードには、複数の側面があります。
-
変数引数 - 異なる引数のセット(型と量の両方)を渡すことができ、関数は渡された引数にマッチした方法で動作します。
-
デフォルトの引数 - 引数が渡されない場合のデフォルト値を定義することができます。
-
名前付き引数 - 引数の順序は関係なくなり、関数に渡したい引数に名前をつけるだけです。
以下は、これらの引数処理の各カテゴリに関するセクションです。
変数引数
javascriptは引数の型チェックや引数の個数を要求しないため、1つの実装で
myFunc()
を実装し、引数の型、存在、量をチェックすることで、どのような引数が渡されたかに適応することができます。
jQueryはいつもこれをやっています。 引数のいくつかをオプションにしたり、渡された引数に応じて関数内を分岐させたりすることができます。
このようなオーバーロードの実装では、いくつかの異なるテクニックを使用することができます。
-
任意の引数の存在を確認するために、宣言された引数名の値が
undefined
. -
総量や引数を確認するには
arguments.length
. - 与えられた引数の型を確認することができます。
-
引数の数が可変の場合は
arguments
擬似配列を使って任意の引数にアクセスすることができます。arguments[i]
.
以下はその例です。
では、jQueryの
obj.data()
メソッドを見てみましょう。 これは4つの異なる使用形態をサポートしています。
obj.data("key");
obj.data("key", value);
obj.data();
obj.data(object);
それぞれが異なる動作を引き起こし、この動的なオーバーロードの形式を使用しない場合、4つの別々の関数が必要になります。
これらすべての選択肢を英語で見分ける方法を説明します。そして、それらをすべてコードで組み合わせてみます。
// get the data element associated with a particular key value
obj.data("key");
に渡される最初の引数が
.data()
に渡される最初の引数が文字列で、2番目の引数が
undefined
である場合、呼び出し元はこのフォームを使用している必要があります。
// set the value associated with a particular key
obj.data("key", value);
第2引数が未定義でない場合、特定のキーの値を設定する。
// get all keys/values
obj.data();
引数が渡されない場合、返されたオブジェクトの全てのキー/値を返します。
// set all keys/values from the passed in object
obj.data(object);
第一引数の型がプレーンオブジェクトの場合、そのオブジェクトの全てのキー/値を設定します。
これらすべてを1セットのjavascriptロジックにまとめる方法を紹介します。
// method declaration for .data()
data: function(key, value) {
if (arguments.length === 0) {
// .data()
// no args passed, return all keys/values in an object
} else if (typeof key === "string") {
// first arg is a string, look at type of second arg
if (typeof value !== "undefined") {
// .data("key", value)
// set the value for a particular key
} else {
// .data("key")
// retrieve a value for a key
}
} else if (typeof key === "object") {
// .data(object)
// set all key/value pairs from this object
} else {
// unsupported arguments passed
}
},
このテクニックの鍵は、受け取りたい引数のすべての形式が一意に識別可能で、呼び出し元がどの形式を使用しているかについて決して混乱しないようにすることです。 これは一般的に、引数を適切に並べ、引数の型と位置に十分な一意性があることを確認し、どの形式が使用されているかを常に見分けることができるようにすることを必要とします。
例えば、3つの文字列引数を取る関数があるとします。
obj.query("firstArg", "secondArg", "thirdArg");
第3引数は簡単にオプションにできるので、その状態を簡単に検出できますが、第2引数は第2引数のつもりなのか、第2引数が省略されたので第2引数の場所にあるものが実は第3引数なのかを識別する方法がないので、呼び出し元がどれを渡すつもりなのか分からないので、第2引数だけをオプションにすることはできません。
obj.query("firstArg", "secondArg");
obj.query("firstArg", "thirdArg");
3つの引数はすべて同じ型なので、異なる引数の違いを見分けることができず、呼び出し側が何を意図しているのかがわかりません。 この呼び出し方だと、第3引数だけ省略可能です。 もし第2引数を省略したければ、それは
null
(または他の検出可能な値)として渡さなければならず、あなたのコードはそれを検出するでしょう。
obj.query("firstArg", null, "thirdArg");
これはオプションの引数のjQueryの例です。両方の引数はオプションで、渡されないとデフォルトの値になります。
clone: function( dataAndEvents, deepDataAndEvents ) {
dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
return this.map( function () {
return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
});
},
これはjQueryの例で、引数がない場合や3つの異なるタイプのいずれかである場合は、4つの異なるオーバーロードを与えることができます。
html: function( value ) {
if ( value === undefined ) {
return this[0] && this[0].nodeType === 1 ?
this[0].innerHTML.replace(rinlinejQuery, "") :
null;
// See if we can take a shortcut and just use innerHTML
} else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
(jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
!wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
value = value.replace(rxhtmlTag, "<$1></$2>");
try {
for ( var i = 0, l = this.length; i < l; i++ ) {
// Remove element nodes and prevent memory leaks
if ( this[i].nodeType === 1 ) {
jQuery.cleanData( this[i].getElementsByTagName("*") );
this[i].innerHTML = value;
}
}
// If using innerHTML throws an exception, use the fallback method
} catch(e) {
this.empty().append( value );
}
} else if ( jQuery.isFunction( value ) ) {
this.each(function(i){
var self = jQuery( this );
self.html( value.call(this, i, self.html()) );
});
} else {
this.empty().append( value );
}
return this;
},
名前付き引数
他の言語(Pythonなど)では、一部の引数のみを渡し、引数が渡される順序に依存しないようにする手段として、名前付き引数を渡すことを許可しています。 Javascript は名前付き引数の機能を直接サポートしていません。 その代わりによく使われるデザインパターンが、プロパティ/値のマップを渡すことです。 これは、プロパティと値を持つオブジェクトを渡すことによって行うことができ、ES6以上では、実際にMapオブジェクト自体を渡すことができます。
以下はES5の簡単な例です。
jQueryの
$.ajax()
は、プロパティと値を持つ通常の Javascript オブジェクトである 1 つのパラメータを渡すだけの使用法を受け入れます。 どのプロパティを渡すかによって、どの引数/オプションがajax呼び出しに渡されるかが決まります。 いくつかは必須ですが、多くはオプションです。 これらはオブジェクトのプロパティであるため、特定の順序はありません。 実際、そのオブジェクトに渡すことができるプロパティは30種類以上ありますが、必須なのは1つ(URL)だけです。
以下はその例です。
$.ajax({url: "http://www.example.com/somepath", data: myArgs, dataType: "json"}).then(function(result) {
// process result here
});
の中は
$.ajax()
実装の内部では、入力されたオブジェクトにどのプロパティが渡されたかを調べ、それらを名前付き引数として使用することができます。 これは
for (prop in obj)
ですべてのプロパティを配列にすることもできます。
Object.keys(obj)
で全てのプロパティを配列に取り込み、その配列を反復する。
この手法は、Javascriptにおいて、多数の引数がある場合や多くの引数がオプションである場合に、非常によく使われます。 注意: これは、引数の最小限の有効なセットが存在することを確認し、不十分な引数が渡された場合、呼び出し側に何が欠けているかのデバッグフィードバックを与えるために、実装関数に責任を負わせます (おそらく役に立つエラーメッセージで例外をスローすることによって)。
ES6環境では、上記の渡されたオブジェクトのデフォルトのプロパティ/値を作成するために、構造化を使用することが可能です。 これは、より詳細には この参照記事 .
その記事から一例を紹介します。
function selectEntries({ start=0, end=-1, step=1 } = {}) {
···
};
では、これをどれかのように呼び出すことができます。
selectEntries({start: 5});
selectEntries({start: 5, end: 10});
selectEntries({start: 5, end: 10, step: 2});
selectEntries({step: 3});
selectEntries();
関数呼び出しの際に列挙しなかった引数は、関数宣言からそのデフォルト値を拾います。
これは、デフォルトのプロパティと値を
start
,
end
と
step
に渡されたオブジェクトのプロパティです。
selectEntries()
関数に渡されるオブジェクトのプロパティです。
関数の引数のデフォルト値
ES6では、Javascriptは引数のデフォルト値に対する組み込みの言語サポートを追加しました。
例えば
function multiply(a, b = 1) {
return a*b;
}
multiply(5); // 5
さらに詳しい使い方を説明する MDN のここ .
関連
-
[解決済み] 配列から特定の項目を削除するにはどうすればよいですか?
-
[解決済み] JavaScriptで "use strict "は何をするのか、その根拠は?
-
[解決済み] JavaScriptで文字列が部分文字列を含むかどうかを確認する方法は?
-
[解決済み] あるJavaScriptファイルを他のJavaScriptファイルにインクルードするにはどうすればよいですか?
-
[解決済み] JavaScriptでメールアドレスを検証するのに最適な方法は何ですか?
-
[解決済み] JavaScriptでタイムスタンプを取得する方法は?
-
[解決済み】別のウェブページにリダイレクトするにはどうすればいいですか?
-
[解決済み】JavaScriptで文字列の出現箇所をすべて置換する方法
-
[解決済み】オブジェクトからプロパティを削除する(JavaScript)
-
[解決済み] なぜjavascriptのES6 Promisesはresolve後も実行を継続するのですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] Javascriptにおける関数のオーバーロード - ベストプラクティス [終了しました]。
-
[解決済み] ジェスト あるクラスの特定のメソッドをモックする方法
-
[解決済み] 文字列のn番目の出現箇所を取得するには?
-
[解決済み] JavaScriptを使用してHTML要素に属性を追加/更新するには?
-
[解決済み] jQueryの$という記号の意味は何ですか?
-
[解決済み] JavaScript で css プロパティを使用して HTML 要素の背景色を設定する方法
-
[解決済み] JavaScriptのArray.sort()メソッドでシャッフルするのは正しいのか?
-
[解決済み] Fetch: ステータスがOKでない場合、プロミスを拒否し、エラーをキャッチするか?
-
[解決済み] リダイレクトされずにHTMLフォームを送信する方法
-
[解決済み] JavaScriptデータフォーマット/プリティプリンタ