1. ホーム
  2. javascript

[解決済み] javascriptで関数をオーバーロードするには?

2022-10-10 22:01:11

質問

オーバーロードに対する古典的な(非js)アプローチ。

function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

Javascriptは同じ名前の関数を複数定義することを許しません。そのため、このようなものが表示されます。

function myFunc(options){
 if(options["overloaded"]){
  //code
 }
}

javascriptの関数のオーバーロードについて、オーバーロードを含むオブジェクトを渡す以外に良い回避策はありますか?

オーバーロードを渡すと、可能性のあるオーバーロードごとに条件文が必要になるため、関数がすぐに冗長になりすぎてしまうことがあります。関数を使用して //code を達成するために関数を使用すると、スコープでトリッキーな状況を引き起こす可能性があります。

どのように解決するのか?

Javascriptの引数のオーバーロードには、複数の側面があります。

  1. 変数引数 - 異なる引数のセット(型と量の両方)を渡すことができ、関数は渡された引数にマッチした方法で動作します。

  2. デフォルトの引数 - 引数が渡されない場合のデフォルト値を定義することができます。

  3. 名前付き引数 - 引数の順序は関係なくなり、関数に渡したい引数に名前をつけるだけです。

以下は、これらの引数処理の各カテゴリに関するセクションです。

変数引数

javascriptは引数の型チェックや引数の個数を要求しないため、1つの実装で myFunc() を実装し、引数の型、存在、量をチェックすることで、どのような引数が渡されたかに適応することができます。

jQueryはいつもこれをやっています。 引数のいくつかをオプションにしたり、渡された引数に応じて関数内を分岐させたりすることができます。

このようなオーバーロードの実装では、いくつかの異なるテクニックを使用することができます。

  1. 任意の引数の存在を確認するために、宣言された引数名の値が undefined .
  2. 総量や引数を確認するには arguments.length .
  3. 与えられた引数の型を確認することができます。
  4. 引数の数が可変の場合は 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 , endstep に渡されたオブジェクトのプロパティです。 selectEntries() 関数に渡されるオブジェクトのプロパティです。

関数の引数のデフォルト値

ES6では、Javascriptは引数のデフォルト値に対する組み込みの言語サポートを追加しました。

例えば

function multiply(a, b = 1) {
  return a*b;
}

multiply(5); // 5

さらに詳しい使い方を説明する MDN のここ .