JavaScriptで関数をオーバーロードする方法は?


103

オーバーロードへの古典的な(非JavaScript)アプローチ:

function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

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

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

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

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


1
次に、javascriptが間違っていると考えています。オーバーロードとスコープに問題がある場合、別のジョブを実行しているように思われるため、おそらく別のメソッド名を使用する必要があります
joshcomley

@joshcomley-オーバーロードとスコープを処理するにはコードが必要です。私が使用しているコードが可能な限り効率的であることを確認しようとしています。回避策でスコープを処理することは問題ありませんが、少なくともベストプラクティスの方法を使用するようにしたいと思います。
Travis J

回答:


126

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

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

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

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

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

可変引数

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

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番目の引数があります。

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の例、または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環境では、構造化を使用して、上記で渡されたオブジェクトのデフォルトのプロパティ/値を作成することができます。これについては、このリファレンス記事で詳しく説明しています

その記事の1つの例を次に示します。

function selectEntries({ start=0, end=-1, step=1 } = {}) {
    ···
};

これにより、のデフォルトのプロパティと値startendおよび関数にstep渡されるオブジェクトのプロパティが作成されますselectEntries()

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

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

例えば:

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

multiply(5); // 5

これをMDNで使用する方法の詳細。


だから私はただ適応可能な関数を使い続け、それを関数のオーバーロードファサードを実装するためのベストプラクティスとして受け入れる必要がありますか?
Travis J

2
@TravisJ-はい、適応可能な関数はJavascriptでオーバーロードが行われる方法です。他のオプションは、異なる引数を持つ個別に名前が付けられた関数を使用することです。2つの実装に共通点がない場合は、どちらも許容できる方法です。2つの実装が本質的に同じことを行っているが、異なる引数で開始する場合、それにより、インターフェイスがよりコンパクトになり、適応可能な関数を使用できるようになり、関連する2つの実装間でコードを共有する機会が得られると思います。
jfriend00

英語と実際のコードの両方で説明されているいくつかのコード例を追加しました。
jfriend00 2014年

これを試してみると、最新バージョンのSafariで予期しないエラー「=」が発生しますresetProgressBar: function(display_errors, lockout = false)
Nick

@Nick-このES6互換性テーブルによれば、デフォルトのパラメーター値はSafari 10以降で機能するはずです。パーこのMDNのページ、それはSafariで「ノーサポート」と言いません。Macを持っていないので、自分でテストすることはできません。これは、一般的に、多くのブラウザで実装されたES6の機能の1つであるようです。たとえば、iOS9のようには見えません。
jfriend00

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;
        }
    }
}

これは代替の良い例です:)
Travis J

1
いいですね、その2番目のテクニックが好きです
Nick Rolando

8

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

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

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

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

function foo(x)
{
} 

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

fooNew(x,y,z)
{
}

次に、1番目の関数を次のように変更します-

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

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

詳細)PS:上記のリンクは、これに関する追加の詳細がある私の個人ブログへのリンクです。


これは問題ではなく、機能です。
inetphantom

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 :)

4

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

function myFunc(options){
 if(typeof options != 'undefined'){
  //code
 }
}

3

ここで説明するこの問題のエレガントなソリューションを開発しようとしました。そして、ここでデモを見つけることができます。使用法は次のようになります。

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;
};

1
入力を処理する関数、非常に賢い。
Travis J

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"

3

JSでのオーバーロードに問題はありません、関数のオーバーロード時にpbはクリーンなコードをどのように維持するのですか?

転送を使用して、次の 2つのことに基づいてクリーンなコードを作成できます。

  1. 引数の数(関数を呼び出すとき)。
  2. 引数のタイプ(関数を呼び出すとき)

      function myFunc(){
          return window['myFunc_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);
       }
    
        /** one argument & this argument is string */
      function myFunc_1_string(){
    
      }
       //------------
       /** one argument & this argument is object */
      function myFunc_1_object(){
    
      }
      //----------
      /** two arguments & those arguments are both string */
      function myFunc_2_string_string(){
    
      }
       //--------
      /** Three arguments & those arguments are : id(number),name(string), callback(function) */
      function myFunc_3_number_string_function(){
                let args=arguments;
                  new Person(args[0],args[1]).onReady(args[3]);
      }
    
       //--- And so on ....   
    


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 + "}";

}

これはオプションオブジェクトの使い方の例です


2

そのためには、関数をオブジェクトに追加する関数を作成する必要があります。その後、関数に送信する引数の量に応じて実行されます。

<script > 
//Main function to add the methods
function addMethod(object, name, fn) {
  var old = object[name];
  object[name] = function(){
    if (fn.length == arguments.length)
      return fn.apply(this, arguments)
    else if (typeof old == 'function')
      return old.apply(this, arguments);
  };
}


  var ninjas = {
   values: ["Dean Edwards", "Sam Stephenson", "Alex Russell"]
};

//Here we declare the first function with no arguments passed
  addMethod(ninjas, "find", function(){
    return this.values;
});

//Second function with one argument
  addMethod(ninjas, "find", function(name){
    var ret = [];
    for (var i = 0; i < this.values.length; i++)
      if (this.values[i].indexOf(name) == 0)
        ret.push(this.values[i]);
    return ret;
  });

//Third function with two arguments
  addMethod(ninjas, "find", function(first, last){
    var ret = [];
    for (var i = 0; i < this.values.length; i++)
      if (this.values[i] == (first + " " + last))
        ret.push(this.values[i]);
    return ret;
  });


//Now you can do:
ninjas.find();
ninjas.find("Sam");
ninjas.find("Dean", "Edwards")
</script>

0

親関数内にサブ関数を追加して、同じ機能の引数グループを区別する機能を実現したいと思います。

var doSomething = function() {
    var foo;
    var bar;
};

doSomething.withArgSet1 = function(arg0, arg1) {
    var obj = new doSomething();
    // do something the first way
    return obj;
};

doSomething.withArgSet2 = function(arg2, arg3) {
    var obj = new doSomething();
    // do something the second way
    return obj;
};

0

達成しようとしていることは、関数のローカル引数変数を使用して行うのが最善です。

function foo() {
    if (arguments.length === 0) {
        //do something
    }
    if (arguments.length === 1) {
        //do something else
    }
}

foo(); //do something
foo('one'); //do something else

これがどのように機能するかについては、こちらで詳しく説明しています


そして、衝突検出はどうですか?これは、パラメーターなしの関数を使用したり、存在を確認したりするのに便利な方法ですが、オーバーロードに関しては実際にはベストプラクティスではありません。
Travis J
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.