111

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

function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

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

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

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

オーバーロードを渡すと、可能なオーバーロードごとに条件ステートメントが必要になるため、関数がすぐに冗長になりすぎる可能性があります。関数を使用して//codeこれらの条件ステートメントの内部を実行すると、スコープでトリッキーな状況が発生する可能性があります。

4

15 に答える 15

133

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

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

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

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

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

可変引数

JavaScript には引数や必要な数の引数の型チェックがないため、引数myFunc()の型、存在、または数をチェックすることで、渡された引数に適応できる実装を 1 つだけ持つことができます。

jQuery は常にこれを行います。引数の一部をオプションにすることも、渡される引数に応じて関数内で分岐することもできます。

これらのタイプのオーバーロードを実装する際には、使用できるさまざまな手法がいくつかあります。

  1. 宣言された引数名の値がundefined.
  2. で合計数量や引数を確認できますarguments.length
  3. 任意の引数の型を確認できます。
  4. 可変数の引数の場合、arguments疑似配列を使用して任意の引数にアクセスできますarguments[i]

ここではいくつかの例を示します。

obj.data()jQueryのメソッドを見てみましょう。次の 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 番目の引数の場所にあるものは実際には 3 番目の引数です。

obj.query("firstArg", "secondArg");
obj.query("firstArg", "thirdArg");

3 つの引数はすべて同じ型であるため、異なる引数の違いを見分けることができないため、呼び出し元が何を意図していたのかわかりません。この呼び出しスタイルでは、3 番目の引数のみを省略できます。2 番目の引数を省略したい場合は、null代わりに as (またはその他の検出可能な値) を渡す必要があり、コードはそれを検出します。

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 の例では、引数が欠落しているか、4 つの異なるオーバーロードを提供する 3 つの異なる型のいずれかである可能性があります。

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 オブジェクトである単一のパラメーターを渡すだけの使用方法を受け入れます。どのプロパティを渡すかによって、どの引数/オプションが 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();

関数呼び出しでリストしない引数は、関数宣言からデフォルト値を取得します。

これにより、関数に渡されるオブジェクトのstartendおよびプロパティの既定のプロパティと値が作成されます。stepselectEntries()

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

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

例えば:

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

multiply(5); // 5

これを使用する方法の詳細については、MDN を参照してください

于 2012-06-01T19:02:59.210 に答える
33

JavaScript での関数のオーバーロードは、さまざまな方法で行うことができます。それらのすべてには、すべてのプロセスを実行するか、サブ機能/プロセスに委任する単一のマスター機能が含まれます。

最も一般的な単純な手法の 1 つに、単純なスイッチが含まれます。

function foo(a, b) {
    switch (arguments.length) {
    case 0:
        //do basic code
        break;
    case 1:
        //do code with `a`
        break;
    case 2:
    default:
        //do code with `a` & `b`
        break;
    }
}

より洗練された手法は、配列 (またはすべての引数カウントに対してオーバーロードを作成していない場合はオブジェクト) を使用することです。

fooArr = [
    function () {
    },
    function (a) {
    },
    function (a,b) {
    }
];
function foo(a, b) {
    return fooArr[arguments.length](a, b);
}

前の例はあまりエレガントではなく、誰でも を変更できfooArr、誰かが に 2 つ以上の引数を渡すと失敗するfooため、より良い形式はモジュール パターンといくつかのチェックを使用することです。

var foo = (function () {
    var fns;
    fns = [
        function () {
        },
        function (a) {
        },
        function (a, b) {
        }
    ];
    function foo(a, b) {
        var fnIndex;
        fnIndex = arguments.length;
        if (fnIndex > foo.length) {
            fnIndex = foo.length;
        }
        return fns[fnIndex].call(this, a, b);
    }
    return foo;
}());

もちろん、オーバーロードで動的な数のパラメーターを使用したい場合があるため、fnsコレクションにオブジェクトを使用できます。

var foo = (function () {
    var fns;
    fns = {};
    fns[0] = function () {
    };
    fns[1] = function (a) {
    };
    fns[2] = function (a, b) {
    };
    fns.params = function (a, b /*, params */) {
    };
    function foo(a, b) {
        var fnIndex;
        fnIndex = arguments.length;
        if (fnIndex > foo.length) {
            fnIndex = 'params';
        }
        return fns[fnIndex].apply(this, Array.prototype.slice.call(arguments));
    }
    return foo;
}());

switch私の個人的な好みは、マスター機能を大きくしますが、傾向があります。この手法を使用する一般的な例は、アクセサー/ミューテーター メソッドです。

function Foo() {} //constructor
Foo.prototype = {
    bar: function (val) {
        switch (arguments.length) {
        case 0:
            return this._bar;
        case 1:
            this._bar = val;
            return this;
        }
    }
}
于 2012-06-01T19:03:57.680 に答える
10

厳密な意味でのメソッドのオーバーロードはできません。javaまたはでサポートされている方法とは異なりc#ます。

問題は、JavaScript がメソッドのオーバーロードをネイティブにサポートしていないことです。そのため、同じ名前の 2 つ以上の関数を認識または解析した場合、最後に定義された関数を考慮して、以前の関数を上書きします。

ほとんどの場合に適していると思う方法の1つは次のとおりです-

メソッドがあるとしましょう

function foo(x)
{
} 

JavaScript では不可能なメソッドをオーバーロードする代わりに、新しいメソッドを定義できます。

fooNew(x,y,z)
{
}

次に、最初の関数を次のように変更します -

function foo(x)
{
  if(arguments.length==2)
  {
     return fooNew(arguments[0],  arguments[1]);
  }
} 

このようなオーバーロードされたメソッドが多数ある場合は、ステートメントswitchだけでなく使用を検討してください。if-else

(詳細) PS: 上記のリンクは、これに関する追加の詳細が記載されている私の個人的なブログに移動します。

于 2014-09-13T06:45:53.200 に答える
4

JavaScript では、関数を 1 回だけ実装し、パラメーターなしで関数を呼び出すことができmyFunc() ます。次に、オプションが「未定義」であるかどうかを確認します。

function myFunc(options){
 if(typeof options != 'undefined'){
  //code
 }
}
于 2012-06-01T19:01:31.817 に答える
4

ここで説明されているこの問題に対するエレガントな解決策を開発しようとしました。デモはこちらからご覧いただけます。使用法は次のようになります。

var out = def({
    'int': function(a) {
        alert('Here is int '+a);
    },

    'float': function(a) {
        alert('Here is float '+a);
    },

    'string': function(a) {
        alert('Here is string '+a);
    },

    'int,string': function(a, b) {
        alert('Here is an int '+a+' and a string '+b);
    },
    'default': function(obj) {
        alert('Here is some other value '+ obj);
    }

});

out('ten');
out(1);
out(2, 'robot');
out(2.5);
out(true);

これを達成するために使用される方法:

var def = function(functions, parent) {
 return function() {
    var types = [];
    var args = [];
    eachArg(arguments, function(i, elem) {
        args.push(elem);
        types.push(whatis(elem));
    });
    if(functions.hasOwnProperty(types.join())) {
        return functions[types.join()].apply(parent, args);
    } else {
        if (typeof functions === 'function')
            return functions.apply(parent, args);
        if (functions.hasOwnProperty('default'))
            return functions['default'].apply(parent, args);        
    }
  };
};

var eachArg = function(args, fn) {
 var i = 0;
 while (args.hasOwnProperty(i)) {
    if(fn !== undefined)
        fn(i, args[i]);
    i++;
 }
 return i-1;
};

var whatis = function(val) {

 if(val === undefined)
    return 'undefined';
 if(val === null)
    return 'null';

 var type = typeof val;

 if(type === 'object') {
    if(val.hasOwnProperty('length') && val.hasOwnProperty('push'))
        return 'array';
    if(val.hasOwnProperty('getDate') && val.hasOwnProperty('toLocaleTimeString'))
        return 'date';
    if(val.hasOwnProperty('toExponential'))
        type = 'number';
    if(val.hasOwnProperty('substring') && val.hasOwnProperty('length'))
        return 'string';
 }

 if(type === 'number') {
    if(val.toString().indexOf('.') > 0)
        return 'float';
    else
        return 'int';
 }

 return type;
};
于 2013-04-10T01:22:09.837 に答える
4

引数の数に基づいて、少し異なるオーバーロード アプローチを使用しています。しかし、ジョン・フォーセットのアプローチも良いと思います。ここでの例は、John Resig (jQuery の作成者) の説明に基づくコードです。

// o = existing object, n = function name, f = function.
    function overload(o, n, f){
        var old = o[n];
        o[n] = function(){
            if(f.length == arguments.length){
                return f.apply(this, arguments);
            }
            else if(typeof o == 'function'){
                return old.apply(this, arguments);
            }
        };
    }

使いやすさ:

var obj = {};
overload(obj, 'function_name', function(){ /* what we will do if no args passed? */});
overload(obj, 'function_name', function(first){ /* what we will do if 1 arg passed? */});
overload(obj, 'function_name', function(first, second){ /* what we will do if 2 args passed? */});
overload(obj, 'function_name', function(first,second,third){ /* what we will do if 3 args passed? */});
//... etc :)
于 2012-12-23T09:21:56.800 に答える
3

https://github.com/jrf0110/leFunc

var getItems = leFunc({
  "string": function(id){
    // Do something
  },
  "string,object": function(id, options){
    // Do something else
  },
  "string,object,function": function(id, options, callback){
    // Do something different
    callback();
  },
  "object,string,function": function(options, message, callback){
    // Do something ca-raaaaazzzy
    callback();
  }
});

getItems("123abc"); // Calls the first function - "string"
getItems("123abc", {poop: true}); // Calls the second function - "string,object"
getItems("123abc", {butt: true}, function(){}); // Calls the third function - "string,object,function"
getItems({butt: true}, "What what?" function(){}); // Calls the fourth function - "object,string,function"
于 2012-06-23T22:05:35.773 に答える
2

これをチェックしてください:

http://www.codeproject.com/Articles/688869/Overloading-JavaScript-Functions

基本的に、クラスでは、オーバーロードする関数に番号を付けてから、1 つの関数呼び出しで、高速かつ簡単に関数のオーバーロードを追加します。

于 2013-12-03T08:44:15.700 に答える
2

JavaScript には関数オーバーロードオプション オブジェクトがないため、代わりに使用できます。必要な引数が 1 つまたは 2 つある場合は、オプション オブジェクトとは別にしておくことをお勧めします。オプション オブジェクトで値が渡されなかった場合に備えて、オプション オブジェクトと入力された値をデフォルト値に使用する方法の例を次に示します。

function optionsObjectTest(x, y, opts) {
    opts = opts || {}; // default to an empty options object

    var stringValue = opts.stringValue || "string default value";
    var boolValue = !!opts.boolValue; // coerces value to boolean with a double negation pattern
    var numericValue = opts.numericValue === undefined ? 123 : opts.numericValue;

    return "{x:" + x + ", y:" + y + ", stringValue:'" + stringValue + "', boolValue:" + boolValue + ", numericValue:" + numericValue + "}";

}

これは、オプション オブジェクトの使用方法の例です。

于 2014-01-09T02:44:30.763 に答える
0

スプレッド演算子をパラメーターとして使用するのはどうですか? 同じブロックを複数のパラメーターで呼び出すことができます。すべてのパラメーターが配列に追加され、メソッド内で長さに基づいてループできます。

    function mName(...opt){
        console.log(opt); 
    }
    mName(1,2,3,4); //[1,2,3,4]
    mName(1,2,3); //[1,2,3]
于 2021-01-01T12:30:58.473 に答える