如何在javascript中重载函数?

How to overload functions in javascript?

重载的经典(非JS)方法:

1
2
3
4
5
6
7
function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

javascript不允许用相同的名称定义多个函数。这样的事情就会出现:

1
2
3
4
5
function myFunc(options){
 if(options["overloaded"]){
  //code
 }
}

在javascript中是否有比传递带有重载的对象更好的函数重载解决方案?

传入重载会很快导致函数变得过于冗长,因为每个可能的重载都需要一个条件语句。使用函数来完成这些条件语句内部的//code,可能会导致作用域出现棘手的情况。


在javascript中,参数重载有多个方面:好的。

  • 变量参数-可以传递不同的参数集(类型和数量),函数的行为方式将与传递给它的参数匹配。好的。

  • 默认参数-如果参数未被传递,则可以为其定义默认值。好的。

  • 命名参数-参数顺序变得不相关,您只需命名要传递给函数的参数。好的。

  • 下面是关于这些类型的参数处理的一节。好的。变量参数

    因为javascript没有对参数或所需数量的参数进行类型检查,所以您只能有一个myFunc()的实现,它可以通过检查参数的类型、存在性或数量来适应传递给它的参数。好的。

    jquery总是这样做。您可以将一些参数设置为可选的,或者根据传递给函数的参数在函数中进行分支。好的。

    在实现这些类型的重载时,可以使用几种不同的技术:好的。

  • 您可以通过检查声明的参数名称值是否为undefined来检查是否存在任何给定的参数。
  • 您可以使用arguments.length检查总数量或参数。
  • 您可以检查任何给定参数的类型。
  • 对于可变数量的参数,可以使用arguments伪数组访问arguments[i]的任何给定参数。
  • 以下是一些例子:好的。

    让我们看看jquery的obj.data()方法。它支持四种不同的使用形式:好的。

    1
    2
    3
    4
    obj.data("key");
    obj.data("key", value);
    obj.data();
    obj.data(object);

    每个函数都会触发不同的行为,如果不使用这种动态形式的重载,则需要四个独立的函数。好的。

    下面是如何用英语区分所有这些选项的方法,然后我将它们全部合并到代码中:好的。

    1
    2
    // get the data element associated with a particular key value
    obj.data("key");

    如果传递给.data()的第一个参数是字符串,而第二个参数是undefined参数,则调用方必须使用此格式。好的。

    1
    2
    // set the value associated with a particular key
    obj.data("key", value);

    如果第二个参数未定义,则设置特定键的值。好的。

    1
    2
    // get all keys/values
    obj.data();

    如果没有传递参数,则返回返回对象中的所有键/值。好的。

    1
    2
    // set all keys/values from the passed in object
    obj.data(object);

    如果第一个参数的类型是普通对象,则设置该对象的所有键/值。好的。

    以下是如何将所有这些组合到一组JavaScript逻辑中:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     // 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
         }
     },

    这种技术的关键是确保您要接受的所有形式的参数都是唯一可识别的,并且不会混淆调用者使用的是哪种形式。这通常需要对参数进行适当的排序,并确保参数的类型和位置具有足够的唯一性,您可以随时知道使用的是哪种形式。好的。

    例如,如果有一个函数接受三个字符串参数:好的。

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

    您可以很容易地将第三个参数设置为可选,并且可以很容易地检测到该条件,但不能仅将第二个参数设置为可选,因为您无法确定调用方要传递的是哪一个参数,因为无法确定第二个参数是指第二个参数,还是忽略了第二个参数,所以第二个论点中的S实际上是第三个论点:好的。

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

    因为这三个参数都是同一类型的,所以您不能区分不同参数之间的差异,所以您不知道调用者想要什么。使用这种调用样式,只有第三个参数可以是可选的。如果您想省略第二个参数,则必须将其作为null或其他可检测值传递,您的代码将检测到:好的。

    1
    obj.query("firstArg", null,"thirdArg");

    下面是一个可选参数的jquery示例。两个参数都是可选的,如果不传递,则采用默认值:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    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示例,其中参数可能会丢失,或者是三种不同类型中的任意一种,它为您提供四种不同的重载:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    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及更高版本中,实际上可以传递映射对象本身。好的。

    下面是一个简单的ES5示例:好的。

    jquery的$.ajax()接受一种使用形式,只需将一个参数传递给它,该参数是一个具有属性和值的常规javascript对象。传递给它的属性决定了哪些参数/选项正在传递给Ajax调用。有些可能是必需的,许多是可选的。因为它们是对象的属性,所以没有特定的顺序。实际上,可以在该对象上传递30多个不同的属性,只需要一个(URL)。好的。

    下面是一个例子:好的。

    1
    2
    3
    $.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
    2
    3
    function selectEntries({ start=0, end=-1, step=1 } = {}) {
        ···
    };

    这将为传递给selectEntries()函数的对象上的startendstep属性创建默认属性和值。好的。函数参数的默认值

    在ES6中,javascript为参数的默认值添加了内置语言支持。好的。

    例如:好的。

    1
    2
    3
    4
    5
    function multiply(a, b = 1) {
      return a*b;
    }

    multiply(5); // 5

    有关在MDN上使用此方法的进一步说明。好的。好啊。


    在javascript中重载函数可以通过多种方式完成。所有这些都涉及一个单独的主函数,它要么执行所有进程,要么委托给子函数/进程。

    最常见的简单技术之一包括一个简单的开关:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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;
        }
    }

    一种更优雅的技术是使用数组(如果不为每个参数计数进行重载,则使用对象):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    fooArr = [
        function () {
        },
        function (a) {
        },
        function (a,b) {
        }
    ];
    function foo(a, b) {
        return fooArr[arguments.length](a, b);
    }

    前一个例子不太优雅,任何人都可以修改fooArr,如果有人向foo传递超过2个参数,它将失败,因此更好的形式是使用模块模式和一些检查:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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集合使用对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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,尽管它确实增加了主功能。我将在何处使用此技术的一个常见示例是访问器/转换器方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function Foo() {} //constructor
    Foo.prototype = {
        bar: function (val) {
            switch (arguments.length) {
            case 0:
                return this._bar;
            case 1:
                this._bar = val;
                return this;
            }
        }
    }


    不能严格执行方法重载。与javac#中支持的方式不同。

    问题是JavaScript本身不支持方法重载。因此,如果它看到/解析具有相同名称的两个或多个函数,它将只考虑最后一个定义的函数并覆盖前面的函数。

    我认为适合大多数情况的方法之一是-

    假设你有方法

    1
    2
    3
    function foo(x)
    {
    }

    您可以定义一个新的方法,而不是在javascript中无法实现的重载方法。

    1
    2
    3
    fooNew(x,y,z)
    {
    }

    然后修改第一个函数如下-

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

    如果您有许多这样的重载方法,请考虑使用switch语句,而不仅仅是if-else语句。

    (更多详细信息)附:上面的链接指向我的个人博客,上面有关于这个的更多细节。


    基于参数编号,我使用了一种稍微不同的重载方法。不过,我相信约翰·福塞特的方法也不错。这里的示例是基于JohnResig(jQuery的作者)的解释的代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 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);
                }
            };
        }

    可用性:

    1
    2
    3
    4
    5
    6
    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 :)


    网址:https://github.com/jrf0110/lefunc

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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"


    我试图为这里描述的这个问题开发一个优雅的解决方案。你可以在这里找到演示。用法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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);

    实现这一目标的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    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;
    };


    在javascript中,您只需实现一次函数并调用不带参数myFunc()的函数,然后检查选项是否"未定义"。

    1
    2
    3
    4
    5
    function myFunc(options){
     if(typeof options != 'undefined'){
      //code
     }
    }

    JS中的重载没有问题,在重载函数时,pb如何维护一个干净的代码?

    基于以下两个方面,您可以使用forward获得干净的代码:

  • 参数数目(调用函数时)。
  • 参数类型(调用函数时)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
      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 ....


  • 为此,需要创建一个将函数添加到对象的函数,然后根据发送给函数的参数数量执行该函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <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")


    因为javascript没有函数重载选项,所以可以使用对象。如果有一个或两个必需的参数,最好将它们与Options对象分开。下面是一个示例,说明如何在选项对象中未传递值的情况下,将选项对象和填充值使用为默认值。

    1
    2
    3
    4
    5
    6
    7
    8
    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 +"}";

    }

    下面是一个关于如何使用选项对象的示例


    看看这个:

    http://www.codeproject.com/articles/688869/overloading-javascript-functions

    基本上,在类中,对要重载的函数进行编号,然后通过一个函数调用添加函数重载,既快速又简单。


    您试图实现的是最好使用函数的局部参数变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function foo() {
        if (arguments.length === 0) {
            //do something
        }
        if (arguments.length === 1) {
            //do something else
        }
    }

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

    你可以在这里找到更好的解释。


    我喜欢在父函数中添加子函数,以实现区分相同功能的参数组的能力。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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;
    };