关于javascript中的函数重载:最佳实践

Function overloading in Javascript - Best practices

在javascript中伪造函数重载的最佳方法是什么?

我知道不可能像其他语言那样重载JavaScript中的函数。如果我需要一个函数,它有两种用法:foo(x)foo(x,y,z),这是最佳/首选的方法:

  • 首先使用不同的名称
  • 使用可选参数,如y = y || 'default'
  • 使用参数个数
  • 检查参数类型
  • 或者如何?

  • 使用参数进行函数重载的最佳方法是不要检查参数长度或类型;检查类型只会使代码变慢,并且可以享受数组、空值、对象等的乐趣。

    大多数开发人员所做的就是将对象作为方法的最后一个参数。这个物体可以容纳任何东西。

    1
    2
    3
    4
    5
    6
    7
    8
    function foo(a, b, opts) {
      // ...
      if (opts['test']) { } //if test param exists, do something..
    }


    foo(1, 2, {"method":"add"});
    foo(3, 4, {"test":"equals","bar":"tree"});

    然后你可以在你的方法中任意处理它。[如有,请切换]


    我经常这样做:

    C:

    1
    2
    3
    4
    5
    6
    7
    public string CatStrings(string p1)                  {return p1;}
    public string CatStrings(string p1, int p2)          {return p1+p2.ToString();}
    public string CatStrings(string p1, int p2, bool p3) {return p1+p2.ToString()+p3.ToString();}

    CatStrings("one");        // result = one
    CatStrings("one",2);      // result = one2
    CatStrings("one",2,true); // result = one2true

    javascript等价物:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function CatStrings(p1, p2, p3)
    {
      var s = p1;
      if(typeof p2 !=="undefined") {s += p2;}
      if(typeof p3 !=="undefined") {s += p3;}
      return s;
    };

    CatStrings("one");        // result = one
    CatStrings("one",2);      // result = one2
    CatStrings("one",2,true); // result = one2true

    这个特殊的例子在JavaScript中实际上比C更优雅。未指定的参数在javascript中是"未定义"的,在if语句中计算结果为false。但是,函数定义没有传递p2和p3是可选的信息。如果需要大量重载,jquery决定使用对象作为参数,例如jquery.ajax(选项)。我同意他们的观点,这是处理过载的最强大和清晰的文档化方法,但我很少需要一个或两个以上的快速可选参数。

    编辑:如果根据伊恩的建议进行测试,则更改


    JavaScript中没有真正的函数重载,因为它允许传递任意类型的参数。您必须在函数内部检查传递了多少参数以及它们是什么类型的。


    正确的答案是JavaScript中没有重载。

    功能内部的检查/切换没有过载。

    过载的概念:在某些编程语言中,函数重载或方法重载是用不同的实现创建多个同名方法的能力。对重载函数的调用将根据调用的上下文运行该函数的特定实现,允许一个函数调用根据上下文执行不同的任务。

    例如,dotask()和dotask(object o)是重载方法。要调用后者,必须将对象作为参数传递,而前者不需要参数,而是使用空参数字段调用。一个常见的错误是为第二个方法中的对象分配一个默认值,这将导致一个不明确的调用错误,因为编译器不知道要使用哪两个方法。

    https://en.wikipedia.org/wiki/function_重载

    所有建议的实现都很好,但说实话,没有针对javascript的本地实现。


    有两种方法可以更好地实现这一点:

  • 如果你想保持很大的灵活性,就传递一个字典(关联数组)

  • 以一个对象为参数,使用基于原型的继承来增加灵活性。


  • 下面是一种允许使用参数类型进行实际方法重载的方法,如下所示:

    1
    2
    3
    4
    Func(new Point());
    Func(new Dimension());
    Func(new Dimension(), new Point());
    Func(0, 0, 0, 0);

    编辑(2018):自2011年编写以来,直接方法调用的速度大大提高,而重载方法的速度却没有提高。

    这不是我推荐的方法,但是思考如何解决这些类型的问题是一个值得思考的练习。

    这里是不同方法的基准-https://jspef.com/function-overloading。从16.0版(beta版)开始,谷歌Chrome的V8中的函数过载(考虑到类型)可能要慢13倍左右。

    除了传递对象(即{x: 0, y: 0})之外,还可以在适当的时候使用c方法,相应地命名方法。例如,vector.addvector(vector)、vector.addingers(x,y,z,…)和vector.addarray(integerarray)。您可以查看C库,例如用于命名灵感的OpenGL。

    编辑:我添加了一个通过对象和使用'param' in argarg.hasOwnProperty('param')测试对象的基准,函数重载比传递对象和检查属性(至少在这个基准中)快得多。

    从设计的角度来看,只有当重载的参数对应于相同的操作时,函数重载才是有效的或逻辑的。因此,有理由认为应该有一个只涉及特定细节的底层方法,否则可能表明不适当的设计选择。因此,还可以通过将数据转换为相应的对象来解决函数重载的使用问题。当然,我们必须考虑问题的范围,因为如果您只想打印一个名称,就不需要进行精心设计,但是对于框架和库的设计,这种想法是合理的。

    我的示例来自一个矩形实现——因此提到了维度和点。也许矩形可以在DimensionPoint原型中添加一个GetRectangle()方法,然后对函数重载问题进行排序。原语呢?我们有参数长度,这是一个有效的测试,因为对象有一个GetRectangle()方法。

    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
    function Dimension() {}
    function Point() {}

    var Util = {};

    Util.Redirect = function (args, func) {
      'use strict';
      var REDIRECT_ARGUMENT_COUNT = 2;

      if(arguments.length - REDIRECT_ARGUMENT_COUNT !== args.length) {
        return null;
      }

      for(var i = REDIRECT_ARGUMENT_COUNT; i < arguments.length; ++i) {
        var argsIndex = i-REDIRECT_ARGUMENT_COUNT;
        var currentArgument = args[argsIndex];
        var currentType = arguments[i];
        if(typeof(currentType) === 'object') {
          currentType = currentType.constructor;
        }
        if(typeof(currentType) === 'number') {
          currentType = 'number';
        }
        if(typeof(currentType) === 'string' && currentType === '') {
          currentType = 'string';
        }
        if(typeof(currentType) === 'function') {
          if(!(currentArgument instanceof currentType)) {
            return null;
          }
        } else {
          if(typeof(currentArgument) !== currentType) {
            return null;
          }
        }
      }
      return [func.apply(this, args)];
    }

    function FuncPoint(point) {}
    function FuncDimension(dimension) {}
    function FuncDimensionPoint(dimension, point) {}
    function FuncXYWidthHeight(x, y, width, height) { }

    function Func() {
      Util.Redirect(arguments, FuncPoint, Point);
      Util.Redirect(arguments, FuncDimension, Dimension);
      Util.Redirect(arguments, FuncDimensionPoint, Dimension, Point);
      Util.Redirect(arguments, FuncXYWidthHeight, 0, 0, 0, 0);
    }

    Func(new Point());
    Func(new Dimension());
    Func(new Dimension(), new Point());
    Func(0, 0, 0, 0);

    最好的方法实际上取决于函数和参数。在不同的情况下,每个选项都是一个好主意。我通常按以下顺序尝试这些方法,直到其中一个有效:

  • 使用可选参数,如y=y"default"。如果可以这样做的话,这很方便,但实际上可能并不总是有效的,例如,当0/null/undefined是有效参数时。

  • 使用参数个数。与上一个选项类似,但可能在1不工作时工作。

  • 正在检查参数类型。在一些参数数目相同的情况下,这可以工作。如果不能可靠地确定类型,则可能需要使用不同的名称。

  • 首先使用不同的名称。如果其他选项不起作用、不实用或与其他相关功能一致,则可能需要执行此操作。


  • If I needed a function with two uses foo(x) and foo(x,y,z) which is the best / preferred way?

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

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

    假设你有方法

    1
    2
    3
    function foo(x)
    {
    }

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

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

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

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

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

    (更多细节)

    附:上面的链接指向我的个人博客,上面有更多的细节。


    我不确定最佳实践,但我是这样做的:

    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
    59
    60
    61
    62
    63
    64
    /*
     * Object Constructor
     */

    var foo = function(x) {
        this.x = x;
    };

    /*
     * Object Protoype
     */

    foo.prototype = {
        /*
         * f is the name that is going to be used to call the various overloaded versions
         */

        f: function() {

            /*
             * Save 'this' in order to use it inside the overloaded functions
             * because there 'this' has a different meaning.
             */
     
            var that = this;  

            /*
             * Define three overloaded functions
             */

            var f1 = function(arg1) {
                console.log("f1 called with" + arg1);
                return arg1 + that.x;
            }

            var f2 = function(arg1, arg2) {
                 console.log("f2 called with" + arg1 +" and" + arg2);
                 return arg1 + arg2 + that.x;
            }

            var f3 = function(arg1) {
                 console.log("f3 called with [" + arg1[0] +"," + arg1[1] +"]");
                 return arg1[0] + arg1[1];
            }

            /*
             * Use the arguments array-like object to decide which function to execute when calling f(...)
             */

            if (arguments.length === 1 && !Array.isArray(arguments[0])) {
                return f1(arguments[0]);
            } else if (arguments.length === 2) {
                return f2(arguments[0], arguments[1]);
            } else if (arguments.length === 1 && Array.isArray(arguments[0])) {
                return f3(arguments[0]);
            }
        }
    }

    /*
     * Instantiate an object
     */

    var obj = new foo("z");

    /*
     * Call the overloaded functions using f(...)
     */

    console.log(obj.f("x"));         // executes f1, returns"xz"
    console.log(obj.f("x","y"));    // executes f2, returns"xyz"
    console.log(obj.f(["x","y"]));  // executes f3, returns"xy"


    我刚试过这个,也许它适合你的需要。根据参数的数量,您可以访问不同的函数。第一次调用它时初始化它。函数映射隐藏在闭包中。

    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
    TEST = {};

    TEST.multiFn = function(){
        // function map for our overloads
        var fnMap = {};

        fnMap[0] = function(){
            console.log("nothing here");
            return this;    //    support chaining
        }

        fnMap[1] = function(arg1){
            //    CODE here...
            console.log("1 arg:"+arg1);
            return this;
        };

        fnMap[2] = function(arg1, arg2){
            //    CODE here...
            console.log("2 args:"+arg1+","+arg2);
            return this;
        };

        fnMap[3] = function(arg1,arg2,arg3){
            //    CODE here...
            console.log("3 args:"+arg1+","+arg2+","+arg3);
            return this;
        };

        console.log("multiFn is now initialized");

        //    redefine the function using the fnMap in the closure
        this.multiFn = function(){
            fnMap[arguments.length].apply(this, arguments);
            return this;
        };

        //    call the function since this code will only run once
        this.multiFn.apply(this, arguments);

        return this;    
    };

    测试它。

    1
    2
    3
    TEST.multiFn("0")
        .multiFn()
        .multiFn("0","1","2");

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

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

    }

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


    另一种方法是使用特殊变量:arguments,这是一个实现:

    1
    2
    3
    4
    5
    6
    7
    function sum() {
        var x = 0;
        for (var i = 0; i < arguments.length; ++i) {
            x += arguments[i];
        }
        return x;
    }

    因此,您可以将此代码修改为:

    1
    2
    3
    4
    5
    6
    7
    8
    function sum(){
        var s = 0;
        if (typeof arguments[0] !=="undefined") s += arguments[0];
    .
    .
    .
        return s;
    }

    介绍

    到目前为止,通读这么多答案会让人头疼。任何想了解这个概念的人都需要了解以下先决条件。

    Function overloading DefinitionFunction Length propertyFunction argument property

    Function overloading的最简单形式意味着函数根据传递给它的参数数目执行不同的任务。值得注意的是,任务1、任务2和任务3在下面突出显示,它们是根据传递给同一个函数fooYoarguments的数目来执行的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // if we have a function defined below
    function fooYo(){
         // do something here
    }
    // on invoking fooYo with different number of arguments it should be capable to do different things

    fooYo();  // does TASK1
    fooYo('sagar'); // does TASK2
    fooYo('sagar','munjal'); // does TAKS3

    注释-JS不提供函数重载的内置功能。

    替代方案

    John E Resig(JS的创建者)指出了一种替代方法,它使用上述前提条件来实现实现函数重载的能力。

    下面的代码通过使用if-elseswitch语句来使用一种简单但天真的方法。

    • 评估argument-length属性。
    • 不同的值导致调用不同的函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var ninja = {
      whatever: function() {
           switch (arguments.length) {
             case 0:
               /* do something */
               break;
             case 1:
               /* do something else */
               break;
             case 2:
               /* do yet something else */
               break;
           //and so on ...
        }
      }
    }

    另一种技术是更干净和动态的。这种技术的亮点是addMethod泛型函数。

    • 我们定义了一个函数addMethod,用于向具有相同名称但功能不同的对象添加不同的函数。

    • addMethod函数下面接受三个参数:对象名object、函数名name和要调用的函数fn

    • addMethod定义中,var old存储了对前一个function的引用,该引用是通过闭合(一个保护性气泡)来存储的。

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

    • 使用调试器了解代码流。
    • addMethod下面添加了三个函数,当使用ninja.whatever(x)和参数x数目调用时,这些函数可以是任何东西,即在使用addMethod函数时,可以是空白的或一个或多个调用不同的函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var ninja = {};
    debugger;


    addMethod(ninja,'whatever',function(){ console.log("I am the one with ZERO arguments supplied") });
    addMethod(ninja,'whatever',function(a){ console.log("I am the one with ONE arguments supplied") });
    addMethod(ninja,'whatever',function(a,b){ console.log("I am the one with TWO arguments supplied") });


    ninja.whatever();
    ninja.whatever(1,2);
    ninja.whatever(3);


    在javascript中没有方法进行函数重载。因此,我建议用typeof()方法代替多个函数来伪造重载。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function multiTypeFunc(param)
    {
        if(typeof param == 'string') {
            alert("I got a string type parameter!!");
         }else if(typeof param == 'number') {
            alert("I got a number type parameter!!");
         }else if(typeof param == 'boolean') {
            alert("I got a boolean type parameter!!");
         }else if(typeof param == 'object') {
            alert("I got a object type parameter!!");
         }else{
            alert("error : the parameter is undefined or null!!");
         }
    }

    祝你好运!


    看看这个。很酷。http://ejohn.org/blog/javascript-method-overloading/使用Javascript进行如下调用:

    1
    2
    3
    4
    var users = new Users();
    users.find(); // Finds all
    users.find("John"); // Finds users by name
    users.find("John","Resig"); // Finds users by first and last name


    因为这篇文章已经包含了很多不同的解决方案,所以我想我会发布另一个解决方案。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function onlyUnique(value, index, self) {
        return self.indexOf(value) === index;
    }

    function overload() {
       var functions = arguments;
       var nroffunctionsarguments = [arguments.length];
        for (var i = 0; i < arguments.length; i++) {
            nroffunctionsarguments[i] = arguments[i].length;
        }
        var unique = nroffunctionsarguments.filter(onlyUnique);
        if (unique.length === arguments.length) {
            return function () {
                var indexoffunction = nroffunctionsarguments.indexOf(arguments.length);
                return functions[indexoffunction].apply(this, arguments);
            }
        }
        else throw new TypeError("There are multiple functions with the same number of parameters");

    }

    如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var createVector = overload(
            function (length) {
                return { x: length / 1.414, y: length / 1.414 };
            },
            function (a, b) {
                return { x: a, y: b };
            },
            function (a, b,c) {
                return { x: a, y: b, z:c};
            }
        );
    console.log(createVector(3, 4));
    console.log(createVector(3, 4,5));
    console.log(createVector(7.07));

    这个解决方案并不完美,但我只想演示如何做到。


    转发模式=>JS重载的最佳实践

    转发到另一个从第3点和第4点构建的函数:

  • Using number of arguments
  • Checking types of arguments
  • 1
    window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)

    您的案件申请:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     function foo(){
              return window['foo_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);

      }
       //------Assuming that `x` , `y` and `z` are String when calling `foo` .

      /**-- for :  foo(x)*/
      function foo_1_string(){
      }
      /**-- for : foo(x,y,z) ---*/
      function foo_3_string_string_string(){

      }

    其他复杂样品:

    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 foo(){
              return window['foo_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);
           }

            /** one argument & this argument is string */
          function foo_1_string(){

          }
           //------------
           /** one argument & this argument is object */
          function foo_1_object(){

          }
          //----------
          /** two arguments & those arguments are both string */
          function foo_2_string_string(){

          }
           //--------
          /** Three arguments & those arguments are : id(number),name(string), callback(function) */
          function foo_3_number_string_function(){
                    let args=arguments;
                      new Person(args[0],args[1]).onReady(args[3]);
          }

           //--- And so on ....

    您现在可以在EcmaScript 2018中进行函数重载,而不需要使用polyfill、检查var长度/类型等,只需使用spread语法即可。

    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
    function foo(var1, var2, opts){
      // set default values for parameters
      const defaultOpts = {
        a: [1,2,3],
        b: true,
        c: 0.3289,
        d:"str",
      }
      // merge default and passed-in parameters
      // defaultOpts must go first!
      const mergedOpts = {...defaultOpts, ...opts};

      // you can now refer to parameters like b as mergedOpts.b,
      // or just assign mergedOpts.b to b
      console.log(mergedOpts.a);
      console.log(mergedOpts.b);
      console.log(mergedOpts.c);  
      console.log(mergedOpts.d);
    }
    // the parameters you passed in override the default ones
    // all JS types are supported: primitives, objects, arrays, functions, etc.
    let var1, var2="random var";
    foo(var1, var2, {a: [1,2], d:"differentString"});

    // parameter values inside foo:
    //a: [1,2]
    //b: true
    //c: 0.3289
    //d:"differentString"

    什么是排列语法?

    The Rest/Spread Properties for ECMAScript proposal (stage 4) adds spread properties to object literals. It copies own enumerable properties from a provided object onto a new object.
    More on mdn

    注意:对象文本中的扩展语法在EDGE和IE中不起作用,它是一个实验特性。请参见浏览器兼容性


    基于动态多态的100行JS函数重载

    • VanillaJS,无外部依赖
    • 全浏览器支持-array.prototype.slice,object.prototype.toString
    • 1114字节uglify'd/744字节g-zipped

    这是一个更大的代码体,其中包括isFnisArr等类型检查功能。下面的vanillaJS版本已经过修改以删除所有外部依赖项,但是您必须定义自己的类型检查函数,以便在.add()调用中使用。

    注:这是一个自执行功能(因此我们可以有一个关闭/关闭范围),因此分配给window.overload,而不是function overload() {...}

    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
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    window.overload = function () {
       "use strict"

        var a_fnOverloads = [],
            _Object_prototype_toString = Object.prototype.toString
        ;

        function isFn(f) {
            return (_Object_prototype_toString.call(f) === '[object Function]');
        } //# isFn

        function isObj(o) {
            return !!(o && o === Object(o));
        } //# isObj

        function isArr(a) {
            return (_Object_prototype_toString.call(a) === '[object Array]');
        } //# isArr

        function mkArr(a) {
            return Array.prototype.slice.call(a);
        } //# mkArr

        function fnCall(fn, vContext, vArguments) {
            //# <ES5 Support for array-like objects
            //#     See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Browser_compatibility
            vArguments = (isArr(vArguments) ? vArguments : mkArr(vArguments));

            if (isFn(fn)) {
                return fn.apply(vContext || this, vArguments);
            }
        } //# fnCall

        //#
        function registerAlias(fnOverload, fn, sAlias) {
            //#
            if (sAlias && !fnOverload[sAlias]) {
                fnOverload[sAlias] = fn;
            }
        } //# registerAlias

        //#
        function overload(vOptions) {
            var oData = (isFn(vOptions) ?
                    { default: vOptions } :
                    (isObj(vOptions) ?
                        vOptions :
                        {
                            default: function (/*arguments*/) {
                                throw"Overload not found for arguments: [" + mkArr(arguments) +"]";
                            }
                        }
                    )
                ),
                fnOverload = function (/*arguments*/) {
                    var oEntry, i, j,
                        a = arguments,
                        oArgumentTests = oData[a.length] || []
                    ;

                    //# Traverse the oArgumentTests for the number of passed a(rguments), defaulting the oEntry at the beginning of each loop
                    for (i = 0; i < oArgumentTests.length; i++) {
                        oEntry = oArgumentTests[i];

                        //# Traverse the passed a(rguments), if a .test for the current oArgumentTests fails, reset oEntry and fall from the a(rgument)s loop
                        for (j = 0; j < a.length; j++) {
                            if (!oArgumentTests[i].tests[j](a[j])) {
                                oEntry = undefined;
                                break;
                            }
                        }

                        //# If all of the a(rgument)s passed the .tests we found our oEntry, so break from the oArgumentTests loop
                        if (oEntry) {
                            break;
                        }
                    }

                    //# If we found our oEntry above, .fn.call its .fn
                    if (oEntry) {
                        oEntry.calls++;
                        return fnCall(oEntry.fn, this, a);
                    }
                    //# Else we were unable to find a matching oArgumentTests oEntry, so .fn.call our .default
                    else {
                        return fnCall(oData.default, this, a);
                    }
                } //# fnOverload
            ;

            //#
            fnOverload.add = function (fn, a_vArgumentTests, sAlias) {
                var i,
                    bValid = isFn(fn),
                    iLen = (isArr(a_vArgumentTests) ? a_vArgumentTests.length : 0)
                ;

                //#
                if (bValid) {
                    //# Traverse the a_vArgumentTests, processinge each to ensure they are functions (or references to )
                    for (i = 0; i < iLen; i++) {
                        if (!isFn(a_vArgumentTests[i])) {
                            bValid = _false;
                        }
                    }
                }

                //# If the a_vArgumentTests are bValid, set the info into oData under the a_vArgumentTests's iLen
                if (bValid) {
                    oData[iLen] = oData[iLen] || [];
                    oData[iLen].push({
                        fn: fn,
                        tests: a_vArgumentTests,
                        calls: 0
                    });

                    //#
                    registerAlias(fnOverload, fn, sAlias);

                    return fnOverload;
                }
                //# Else one of the passed arguments was not bValid, so throw the error
                else {
                    throw"poly.overload: All tests must be functions or strings referencing `is.*`.";
                }
            }; //# overload*.add

            //#
            fnOverload.list = function (iArgumentCount) {
                return (arguments.length > 0 ? oData[iArgumentCount] || [] : oData);
            }; //# overload*.list

            //#
            a_fnOverloads.push(fnOverload);
            registerAlias(fnOverload, oData.default,"default");

            return fnOverload;
        } //# overload

        //#
        overload.is = function (fnTarget) {
            return (a_fnOverloads.indexOf(fnTarget) > -1);
        } //# overload.is

        return overload;
    }();

    用途:

    调用者通过给overload()的返回分配一个变量来定义其重载函数。通过链接,可以在以下系列中定义额外的重载:

    1
    2
    3
    4
    var myOverloadedFn = overload(function(){ console.log("default", arguments) })
        .add(function(){ console.log("noArgs", arguments) }, [],"noArgs")
        .add(function(){ console.log("str", arguments) }, [function(s){ return typeof s === 'string' }],"str")
    ;

    overload()的单个可选参数定义了在无法识别签名时要调用的"默认"函数。.add()的论点是:

  • fnfunction定义过载;
  • a_vArgumentTestsfunctionArray定义在arguments上运行的测试。每个function接受一个参数,并根据参数是否有效返回truethy;
  • sAlias(可选):string定义别名直接访问重载函数(fn,如myOverloadedFn.noArgs()直接调用该函数,避免了参数的动态多态性测试。
  • 这种实现实际上允许的不仅仅是传统的函数重载,因为实际中.add()的第二个a_vArgumentTests参数定义了自定义类型。因此,您不仅可以基于类型,还可以基于范围、值或值集合来筛选参数!

    如果您查看overload()的145行代码,您将看到每个签名都按传递给它的arguments的编号进行分类。这样做是为了限制正在运行的测试的数量。我也会记录通话记录。使用一些额外的代码,可以对重载函数的数组进行重新排序,以便首先测试更常见的函数,然后再添加一些性能增强的度量。

    现在,有一些警告……由于Javascript是松散类型的,因此您必须小心使用vArgumentTests,因为integer可以验证为float等。

    jscompress.com版本(1114字节,744字节g-zipped):

    1
    window.overload=function(){'use strict';function b(n){return'[object Function]'===m.call(n)}function c(n){return!!(n&&n===Object(n))}function d(n){return'[object Array]'===m.call(n)}function e(n){return Array.prototype.slice.call(n)}function g(n,p,q){if(q=d(q)?q:e(q),b(n))return n.apply(p||this,q)}function h(n,p,q){q&&!n[q]&&(n[q]=p)}function k(n){var p=b(n)?{default:n}:c(n)?n:{default:function(){throw'Overload not found for arguments: ['+e(arguments)+']'}},q=function(){var r,s,t,u=arguments,v=p[u.length]||[];for(s=0;s<v.length;s++){for(r=v[s],t=0;t<u.length;t++)if(!v[s].tests[t](u[t])){r=void 0;break}if(r)break}return r?(r.calls++,g(r.fn,this,u)):g(p.default,this,u)};return q.add=function(r,s,t){var u,v=b(r),w=d(s)?s.length:0;if(v)for(u=0;u<w;u++)b(s[u])||(v=_false);if(v)return p[w]=p[w]||[],p[w].push({fn:r,tests:s,calls:0}),h(q,r,t),q;throw'poly.overload: All tests must be functions or strings referencing `is.*`.'},q.list=function(r){return 0<arguments.length?p[r]||[]:p},l.push(q),h(q,p.default,'default'),q}var l=[],m=Object.prototype.toString;return k.is=function(n){return-1<l.indexOf(n)},k}();


    您可以从john resig中使用"addmethod"。使用此方法,可以基于参数计数"重载"方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // addMethod - By John Resig (MIT Licensed)
    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 );
        };
    }

    我还创建了一个替代方法来使用缓存来保存函数的变化。这里描述了不同之处

    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
    // addMethod - By Stavros Ioannidis
    function addMethod(obj, name, fn) {
      obj[name] = obj[name] || function() {
        // get the cached method with arguments.length arguments
        var method = obj[name].cache[arguments.length];

        // if method exists call it
        if ( !! method)
          return method.apply(this, arguments);
        else throw new Error("Wrong number of arguments");
      };

      // initialize obj[name].cache
      obj[name].cache = obj[name].cache || {};

      // Check if a method with the same number of arguments exists  
      if ( !! obj[name].cache[fn.length])
        throw new Error("Cannot define multiple '" + name +
         "' methods with the same number of arguments!");

      // cache the method with fn.length arguments
      obj[name].cache[fn.length] = function() {
        return fn.apply(this, arguments);
      };
    }

    对于您的用例,这就是我将如何使用ES6来处理它(因为它已经是2017年底的时候了):

    1
    2
    3
    4
    5
    6
    7
    8
    const foo = (x, y, z) => {
      if (y && z) {
        // Do your foo(x, y, z); functionality
        return output;
      }
      // Do your foo(x); functionality
      return output;
    }

    显然,您可以将其调整为使用任意数量的参数,并相应地更改条件语句。


    截至2017年7月,以下是常用的技术。注意,我们也可以在函数中执行类型检查。

    1
    2
    3
    4
    5
    function f(...rest){   // rest is an array
       console.log(rest.length);
       for (v of rest) if (typeof(v)=="number")console.log(v);
    }
    f(1,2,3);  // 3 1 2 3

    这是一个旧问题,但我认为需要另一个条目(尽管我怀疑任何人都会读)。可以将立即调用的函数表达式(IIFE)与闭包和内联函数一起使用,以允许函数重载。请考虑以下(人为)示例:

    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
    var foo;

    // original 'foo' definition
    foo = function(a) {
      console.log("a:" + a);
    }

    // define 'foo' to accept two arguments
    foo = (function() {
      // store a reference to the previous definition of 'foo'
      var old = foo;

      // use inline function so that you can refer to it internally
      return function newFoo(a,b) {

        // check that the arguments.length == the number of arguments
        // defined for 'newFoo'
        if (arguments.length == newFoo.length) {
          console.log("a:" + a);
          console.log("b:" + b);

        // else if 'old' is a function, apply it to the arguments
        } else if (({}).toString.call(old) === '[object Function]') {
          old.apply(null, arguments);
        }
      }
    })();

    foo(1);
    > a: 1
    foo(1,2);
    > a: 1
    > b: 2
    foo(1,2,3)
    > a: 1

    简而言之,IIFE的使用创建了一个局部范围,允许我们定义私有变量old,以存储对函数foo初始定义的引用。然后,此函数返回一个内联函数newFoo,如果它恰好传递了两个参数ab,则记录两个参数的内容,或者如果arguments.length !== 2,则调用old函数。这个模式可以重复任意多次,以赋予一个变量几个不同的函数定义。


    第一个选项确实值得注意,因为它是我在非常复杂的代码设置中提出的。所以,我的答案是

  • Using different names in the first place
  • 有了一点基本的提示,计算机的名字应该看起来不一样,但对你来说不一样。名称重载函数,如:func、func1、func2。


    javascript是非类型化的语言,我只认为根据参数的数量重载方法/函数是有意义的。因此,我建议检查参数是否已定义:

    1
    2
    3
    4
    5
    6
    7
    8
    myFunction = function(a, b, c) {
         if (b === undefined && c === undefined ){
              // do x...
         }
         else {
              // do y...
         }
    };


    我们通过.js来解决这个问题是一种非常优雅的方式。你可以做到:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var obj = {

      /**
       * Says something in the console.
       *
       * say(msg) - Says something once.
       * say(msg, times) - Says something many times.
       */

      say: Over(
        function(msg$string){
          console.info(msg$string);
        },
        function(msg$string, times$number){
          for (var i = 0; i < times$number; i++) this.say(msg$string);
        }
      )

    };

    我喜欢@antouank的方法。我经常发现自己提供的函数具有不同的数字o参数和不同的类型。有时他们不服从命令。我用来映射参数类型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    findUDPServers: function(socketProperties, success, error) {
        var fqnMap = [];

        fqnMap['undefined'] = fqnMap['function'] = function(success, error) {
            var socketProperties = {name:'HELLO_SERVER'};

            this.searchServers(socketProperties, success, error);
        };

        fqnMap['object'] = function(socketProperties, success, error) {
            var _socketProperties = _.merge({name:'HELLO_SERVER'}, socketProperties || {});

            this.searchServers(_socketProperties, success, error);
        };

        fqnMap[typeof arguments[0]].apply(this, arguments);
    }

    所以我真的很喜欢用这种方式来做我在javascript忍者秘籍中发现的事情。

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

    然后使用addmethod向任何对象添加重载函数。对我来说,这段代码中的主要混淆是使用了fn.length==arguments.length——这很有效,因为fn.length是预期参数的数量,而arguments.length是用函数实际调用的参数的数量。匿名函数没有参数的原因是,您可以用JavaScript传递任意数量的参数,并且该语言可以被原谅。

    我喜欢这个,因为你可以在任何地方使用它——只需创建这个函数,并在任何你想要的代码库中简单地使用这个方法。

    它还避免了一个非常大的if/switch语句,如果您开始编写复杂的代码,这将变得难以读取(接受的答案将导致这种情况)。

    在缺点方面,我猜代码最初有点模糊……但我不确定其他人?


    我想分享一个类似于重载的方法的有用示例。

    1
    2
    3
    4
    5
    6
    7
    function Clear(control)
    {
      var o = typeof control !=="undefined" ? control : document.body;
      var children = o.childNodes;
      while (o.childNodes.length > 0)
        o.removeChild(o.firstChild);
    }

    用途:clear();//清除所有文档

    clear(mydiv);//清除mydiv引用的面板


    多年来,我一直使用此函数来美化我的重载:

    1
    2
    3
    4
    5
    6
    7
    function overload(){
      const fs = arguments, fallback = fs[fs.length - 1];
      return function(){
        const f = fs[arguments.length] || (arguments.length >= fs.length ? fallback : null);
        return f.apply(this, arguments);
      }
    }

    Demostrated:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function curry1(f){
      return curry2(f, f.length);
    }

    function curry2(f, minimum){
      return function(...applied){
        if (applied.length >= minimum) {
          return f.apply(this, applied);
        } else {
          return curry2(function(...args){
            return f.apply(this, applied.concat(args));
          }, minimum - applied.length);
        }
      }
    }

    export const curry = overload(null, curry1, curry2);

    看看jquery的off方法:

    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
      function off( types, selector, fn ) {
        var handleObj, type;
        if ( types && types.preventDefault && types.handleObj ) {

            // ( event )  dispatched jQuery.Event
            handleObj = types.handleObj;
            jQuery( types.delegateTarget ).off(
                handleObj.namespace ?
                    handleObj.origType +"." + handleObj.namespace :
                    handleObj.origType,
                handleObj.selector,
                handleObj.handler
            );
            return this;
        }
        if ( typeof types ==="object" ) {

            // ( types-object [, selector] )
            for ( type in types ) {
                this.off( type, selector, types[ type ] );
            }
            return this;
        }
        if ( selector === false || typeof selector ==="function" ) {

            // ( types [, fn] )
            fn = selector;
            selector = undefined;
        }
        if ( fn === false ) {
            fn = returnFalse;
        }
        return this.each( function() {
            jQuery.event.remove( this, types, fn, selector );
        } );
      }

    许多为性能优化而重载的函数几乎是不可读的。你必须破译函数的头部。这可能比使用我提供的overload函数要快;但是,从人类的角度来看,在识别调用哪个重载方面要慢。


    对于函数重载,可以这样做。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function addCSS(el, prop, val) {
      return {
        2: function() {
          // when two arguments are set
          // now prop is an oject
          for (var i in prop) {
              el.style[i] = prop[i];
          }
        },
        3: function() {
          // when three arguments are set
          el.style[prop] = val;
        }
        }[arguments.length]();
    }
    // usage
    var el = document.getElementById("demo");
    addCSS(el,"color","blue");
    addCSS(el, {
       "backgroundColor":"black",
     "padding":"10px"
    });

    来源


    JS中没有实际的重载,但是我们仍然可以通过几种方式模拟方法重载:

    方法1:使用对象

    1
    2
    3
    4
    5
    6
    function test(x,options){
      if("a" in options)doSomething();
      else if("b" in options)doSomethingElse();
    }
    test("ok",{a:1});
    test("ok",{b:"string"});

    方法2:使用静止(排列)参数

    1
    2
    3
    4
    5
    function test(x,...p){
     if(p[2])console.log("3 params passed"); //or if(typeof p[2]=="string")
    else if (p[1])console.log("2 params passed");
    else console.log("1 param passed");
    }

    方法3:未定义使用

    1
    2
    3
    function test(x, y, z){
     if(typeof(z)=="undefined")doSomething();
    }

    方法4:类型检查

    1
    2
    3
    4
    function test(x){
     if(typeof(x)=="string")console.log("a string passed")
     else ...
    }

    我正在开发一个库,它为javascript提供类代码功能,目前它支持构造函数、继承、按参数数量和参数类型重载方法、混合、静态属性和单例。

    请参阅使用该库的方法重载示例:

    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
    eutsiv.define('My.Class', {
        constructor: function() {
            this.y = 2;
        },
        x: 3,
        sum: function() {
            return this.x + this.y;
        },
        overloads: {
            value: [
                function() { return this.x + ', ' + this.y },
                function(p1) { this.x = p1; },
                function(p1, p2) { this.x = p1; this.y = p2; }  // will set x and y
            ]
        }
    });

    var test = new My.Class({ x: 5 });   // create the object
    test.value();                        // will return '5, 2'
    test.sum();                          // will return 7
    test.value(13);                      // will set x to 13
    test.value();                        // will return '13, 2'
    test.sum();                          // will return 15
    test.value(10, 20);                  // will set x to 10 and y to 20
    test.value();                        // will return '10, 20'
    test.sum();                          // will return 30

    欢迎任何反馈、错误修复、文档和测试改进!

    https://github.com/eutsiv/eutsiv.js网站