【问题标题】:Function overloading in Javascript - Best practices [closed]Javascript中的函数重载-最佳实践[关闭]
【发布时间】:2010-10-02 03:41:51
【问题描述】:

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

我知道在 Javascript 中无法像在其他语言中那样重载函数。 如果我需要一个有两个用途的函数 foo(x)foo(x,y,z) 这是最好/首选的方式:

  1. 首先使用不同的名称
  2. 使用可选参数,如y = y || 'default'
  3. 使用参数数量
  4. 检查参数类型
  5. 或者如何?

【问题讨论】:

  • 也许问问你为什么认为你需要函数重载开始会很有用。我认为这将使我们更接近真正的解决方案。
  • 这是关闭的,但我执行以下操作: this.selectBy = { instance: selectByInstance, // 函数文本: selectByText, // 函数值: selectByValue // 函数 };
  • 我的回答显示了如何进行运行时函数重载,它有速度损失,我不建议这样做来绕过 Javascript 的规范。函数重载实际上是一个编译时任务,我只是出于学术目的提供答案,是否在代码中使用它由您自行决定。
  • 以防万一它有用,我构建了一个轻量级的 js 框架,它允许基于类型的方法重载。显然,同样的警告适用于性能,但到目前为止它已经很好地满足了我的需求,并且仍有很大的改进空间:blog.pebbl.co.uk/2013/01/describejs.html#methodoverloading

标签: javascript overloading


【解决方案1】:

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

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

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

然后你可以在你的方法中以任何你想要的方式处理它。 [开关、if-else 等]

【讨论】:

  • 您能否提供一个 foo() 的示例实现来说明如何使用/引用这些“opts”参数?
  • 萌 // 可能是这样的; if(opts['test']) //if test param exists, do something.. if(opts['bar']) //if bar param exists, do something
  • 这不是函数重载。函数重载是有两个独立的函数,名称相同但参数不同。您所描述的只是一个最后带有对象参数的函数。
  • @user1334007 不可能像在 Java/.NET 中那样进行函数重载。是的,这不是“完全”的重载,但它可以完成这项工作。
  • 我很惊讶没有人问过这个问题:为什么不推荐检查arguments.length?另外,我以前来过这里并阅读过大多数开发人员所做的是...,但我确信这是我见过的唯一地方。该方法还破坏了“重载”的语法糖度!
【解决方案2】:

我经常这样做:

C#:

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 等效项:

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(options)。我同意他们的观点,这是最强大且可清楚记录的重载方法,但我很少需要超过一两个快速可选参数。

编辑:根据 Ian 的建议更改了 IF 测试

【讨论】:

  • 未指定的参数是JS中的undefined,而不是null。作为最佳实践,您永远不应将任何内容设置为 undefined,因此只要将测试更改为 p2 === undefined 就不会出现问题。
  • 如果您将false 作为最后一个参数传递,那么它不会将"false" 连接到末尾,因为if(p3) 不会分支。
  • 请注意,您的typeof p2 === "undefined" 可能与您在示例实例中所期望的相反,我认为typeof p2 !== "undefined" 是您想要的。另外,我可以建议它应该连接你实际做的字符串、数字和布尔值p2 === "number"; p3 === "boolean"
  • 我喜欢这样做:p3 = p3 || '默认值';
  • ===!==是什么意思?为什么不直接使用==!=
【解决方案3】:

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

【讨论】:

  • John Resig(来自 jQuery)曾经尝试过这个,但这种尝试纯粹是学术性的,并没有提供任何真正的好处。
  • John Resig 的函数重载在这里 ejohn.org/blog/javascript-method-overloading
  • @Terrance:我也喜欢 Resig 的方法。它就像一个魅力。我只需要找到一种方法来为其创建测试以验证用例。
  • “这个函数不会改变世界,但它简短、简洁,并且使用了一个不起眼的 JavaScript 特性——所以它在我的书中获胜。” :-)
【解决方案4】:

正确答案是JAVASCRIPT没有重载。

函数内部的检查/切换不是OVERLOADING。

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

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

https://en.wikipedia.org/wiki/Function_overloading

所有建议的实现都很棒,但说实话,JavaScript 没有原生实现。

【讨论】:

  • 终于有正常答案了! JAVASCRIPT 没有重载。
  • OP 要求提供一种 fake 重载的方法。
  • 正如我之前所说,我们在这里是为了教育人们,我们不只是在没有验证他们所问的问题是否正确的情况下给出答案。
  • 当你应该专注于预期含义时与人类争论字面意义就像与编译器争论你的预期含义时它只响应代码的字面意义一样是错误的。 OP 的目的显然是在调用站点之间实现与重载提供的类似效果,同时承认 JS 不支持该构造。
  • 87 人赞成,就是这样。模拟这样的功能只是浪费时间。不管你喜不喜欢,JavaScript 没有这个特性,所以人们应该知道。
【解决方案5】:

有两种方法可以更好地解决这个问题:

  1. 如果您想保留很大的灵活性,请传递字典(关联数组)

  2. 将对象作为参数并使用基于原型的继承来增加灵活性。

【讨论】:

  • 这是我最初的想法,但是,如果您创建的函数要在库中或由其他人使用,那么简单地枚举值可能会有所帮助
【解决方案6】:

这是一种允许使用参数类型重载真实方法的方法,如下所示:

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

Edit (2018):由于这是 2011 年编写的,直接方法调用的速度大大提高,而重载方法的速度却没有。

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


这里是不同方法的基准 - https://jsperf.com/function-overloading。它表明,截至 16.0(beta),在 Google Chrome 的 V8 中,函数重载(考虑到类型)可能会慢 13 倍

除了传递一个对象(即{x: 0, y: 0})外,还可以在适当的时候采用 C 方法,相应地命名方法。例如,Vector.AddVector(vector)、Vector.AddIntegers(x, y, z, ...) 和 Vector.AddArray(integerArray)。您可以查看 C 库,例如 OpenGL,以获得命名灵感。

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

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

我的示例来自 Rectangle 实现 - 因此提到了 Dimension 和 Point。或许Rectangle可以在DimensionPoint原型上加一个GetRectangle()方法,然后函数重载问题就解决了。那么原始人呢?好吧,我们有参数长度,现在这是一个有效的测试,因为对象有一个GetRectangle() 方法。

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

【讨论】:

    【解决方案7】:

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

    1. 使用可选参数,例如 y = y || 'default'。 如果你能做到,这很方便,但它可能并不总是有效,例如当 0/null/undefined 是一个有效的参数时。

    2. 使用参数数量。类似于最后一个选项,但在 #1 不起作用时可能会起作用。

    3. 检查参数类型。这在参数数量相同的某些情况下可能有效。如果您无法可靠地确定类型,则可能需要使用不同的名称。

    4. 首先使用不同的名称。如果其他选项不起作用、不实用或与其他相关功能保持一致,您可能需要这样做。

    【讨论】:

      【解决方案8】:

      如果我需要一个有两个用途的函数 foo(x) 和 foo(x,y,z) 哪个是最好/首选的方式?

      问题在于 JavaScript 本身并不支持方法重载。因此,如果它看到/解析两个或多个同名函数,它只会考虑最后定义的函数并覆盖之前的函数。

      我认为适合大多数情况的一种方式如下-

      假设你有方法

      function foo(x)
      {
      } 
      

      您可以定义一个新方法

      ,而不是重载方法这在javascript中是不可能的
      fooNew(x,y,z)
      {
      }
      

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

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

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

      【讨论】:

        【解决方案9】:

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

        /*
         * 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"
        

        【讨论】:

        • @Luis : 我添加了一些希望对您有帮助的 cmets。
        【解决方案10】:

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

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

        测试一下。

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

        【讨论】:

          【解决方案11】:

          不是每个人都知道您可以直接在函数签名中执行Destructuring assignment

          因此,您可以轻松定义一个非常灵活的方法签名,恕我直言,它优于 Java 方法重载。

          例子:

          const myFunction = (({a, b, c}) => {
              console.log(a, b, c);
          });
          
          myFunction({a: 1, b: 2});
          myFunction({a: 1, b: 2, c: 3});
          
          

          你甚至不需要尊重参数的顺序,调用语句和目标方法签名之间存在命名一致性。

          您也可以指定默认值

          const myFunction = (({a = 1, b = 2, c}) => {
              console.log(a, b, c);
          });
          

          【讨论】:

            【解决方案12】:

            由于 JavaScript 没有函数重载选项,因此可以使用对象来代替。如果有一个或两个必需参数,最好将它们与选项对象分开。这是一个关于如何使用选项对象并将值填充为默认值的示例,以防选项对象中未传递值。

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

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

            【讨论】:

              【解决方案13】:

              简介

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

              Function overloading DefinitionFunction Length propertyFunction argument property

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

              // 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 属性。
              • 不同的值会导致调用不同的函数。

              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 的引用,该引用在闭包的帮助下存储 - 一个保护气泡。

              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 调用这些函数时,可以是任何值,即空白或一个或多个调用在使用@ 时定义的不同函数987654345@函数。

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

              【讨论】:

                【解决方案14】:

                在 javascript 中无法实现函数重载。 所以,我建议通过typeof() 方法而不是 多个函数来伪造重载。

                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!!");
                     }
                }
                

                祝你好运!

                【讨论】:

                • 看在上帝的份上!使用 switch 语句!
                • 另外,如果你坚持不使用开关,你应该只调用一次 typeof。 var type = typeof param; if (type === 'string') ...
                • +1 评论“===”。 switch 语句相对于 if (...==...) 的另一个优点是它是类型安全的。
                【解决方案15】:

                解决这个问题的另一种方法是使用特殊变量:arguments,这是一个实现:

                function sum() {
                    var x = 0;
                    for (var i = 0; i < arguments.length; ++i) {
                        x += arguments[i];
                    }
                    return x;
                }
                

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

                function sum(){
                    var s = 0;
                    if (typeof arguments[0] !== "undefined") s += arguments[0];
                .
                .
                .
                    return s;
                }
                

                【讨论】:

                  【解决方案16】:

                  看看这个。这很酷。 http://ejohn.org/blog/javascript-method-overloading/ Trick Javascript 允许您执行如下调用:

                  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
                  

                  【讨论】:

                  • 嗨 Jaider,看看我的回答,它包含 actual javascript 方法重载的代码。我说的是Func(new Point())Func(new Rectangle()) 将执行不同的功能。但我必须指出,这是一个肮脏的 hack,因为方法重载实际上是编译时任务而不是运行时。
                  【解决方案17】:

                  #Forwarding Pattern => JS 重载的最佳实践 转发到另一个函数,其名称是从第 3 点和第 4 点构建的:

                  1. 使用参数数量
                  2. 检查参数类型
                  window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)
                  

                  #应用你的案例:

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

                  #其他复杂样本:

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

                  【讨论】:

                    【解决方案18】:

                    由于这篇文章已经包含了很多不同的解决方案,我想我再发布一个。

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

                    这个可以如下图使用:

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

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

                    【讨论】:

                      【解决方案19】:

                      您可以使用 John Resig 的“addMethod”。使用此方法,您可以根据参数计数“重载”方法。

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

                      我还创建了此方法的替代方法,它使用缓存来保存函数的变体。 The differencies are described here

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

                      【讨论】:

                        【解决方案20】:

                        100行JS通过动态多态实现函数重载

                        这是来自更大的代码体,其中包括isFnisArr 等类型检查函数。下面的 VanillaJS 版本已经过重新设计以删除所有外部依赖项,但是您必须定义自己的类型检查函数以在 .add() 调用中使用。

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

                        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() 的返回值来定义它们的重载函数。多亏了链接,可以串联定义额外的重载:

                        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() 的参数是:

                        1. fn: function 定义重载;
                        2. a_vArgumentTestsfunctions 中的 Array 定义要在 arguments 上运行的测试。每个function 接受一个参数并根据参数是否有效返回truethy;
                        3. sAlias(可选):string 定义别名以直接访问重载函数(fn),例如myOverloadedFn.noArgs() 将直接调用该函数,避免参数的动态多态性测试。

                        这个实现实际上不仅仅允许传统的函数重载,因为.add() 的第二个a_vArgumentTests 参数实际上定义了自定义类型。因此,您不仅可以基于类型,还可以基于范围、值或值集合来对参数进行门控!

                        如果您查看 overload() 的 145 行代码,您会发现每个签名都按传递给它的 arguments 的数量进行分类。这样做是为了限制我们正在运行的测试数量。我还跟踪呼叫计数。通过一些额外的代码,重载函数的数组可以重新排序,以便首先测试更常用的函数,再次添加一些性能增强措施。

                        现在,有一些注意事项...由于 Javascript 的类型很松散,因此您必须小心 vArgumentTests,因为 integer 可能会被验证为 float,等等。

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

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

                        【讨论】:

                          【解决方案21】:

                          您现在可以在 ECMAScript 2018 中进行函数重载,无需 polyfill、检查 var 长度/类型等,只需使用 spread syntax

                          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"

                          什么是展开语法?

                          ECMAScript 提案(第 4 阶段)的 Rest/Spread 属性将扩展属性添加到对象字面量。它将自己的可枚举属性从提供的对象复制到新对象上。 More on mdn

                          注意:对象字面量中的扩展语法在 Edge 和 IE 中不起作用,它是一个实验性功能。 see browser compatability

                          【讨论】:

                            【解决方案22】:

                            JS 中并没有真正的重载,无论如何我们仍然可以通过几种方式模拟方法重载:

                            方法#1: 使用对象

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

                            方法#2: 使用剩余(传播)参数

                            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: 使用未定义

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

                            方法#4: 类型检查

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

                            【讨论】:

                              【解决方案23】:

                              可以为函数重载做类似的事情。

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

                              Source

                              【讨论】:

                                【解决方案24】:

                                Javascript 中的函数重载:

                                函数重载是一种编程语言创建具有不同实现的多个同名函数的能力。当调用重载函数时,它将运行适合于调用上下文的该函数的特定实现。这个上下文通常是接收的参数数量,它允许一个函数调用根据上下文表现不同。

                                Javascript没有内置函数重载。但是,可以通过多种方式模拟此行为。这是一个方便的简单方法:

                                function sayHi(a, b) {
                                  console.log('hi there ' + a);
                                  if (b) { console.log('and ' + b) } // if the parameter is present, execute the block
                                }
                                
                                sayHi('Frank', 'Willem');

                                在您不知道将获得多少个参数的情况下,您可以使用 rest 运算符,即三个点 ...。它将剩余的参数转换成一个数组。不过要注意浏览器的兼容性。这是一个例子:

                                function foo (a, ...b) {
                                  console.log(b);
                                }
                                
                                foo(1,2,3,4);
                                foo(1,2);

                                【讨论】:

                                  【解决方案25】:

                                  虽然 默认参数 不会重载,但它可能会解决开发人员在该领域面临的一些问题。输入严格由顺序决定,您不能像经典重载那样随意重新排序:

                                  function transformer(
                                      firstNumber = 1,
                                      secondNumber = new Date().getFullYear(),
                                      transform = function multiply(firstNumber, secondNumber) {
                                          return firstNumber * secondNumber;
                                      }
                                  ) {
                                      return transform(firstNumber, secondNumber);
                                  }
                                  
                                  console.info(transformer());
                                  console.info(transformer(8));
                                  console.info(transformer(2, 6));
                                  console.info(transformer(undefined, 65));
                                  
                                  function add(firstNumber, secondNumber) {
                                      return firstNumber + secondNumber;
                                  }
                                  console.info(transformer(undefined, undefined, add));
                                  console.info(transformer(3, undefined, add));
                                  

                                  结果(2020 年):

                                  2020
                                  16160
                                  12
                                  65
                                  2021
                                  2023
                                  

                                  更多信息:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters

                                  【讨论】:

                                    【解决方案26】:

                                    我们提出over.js 来解决这个问题是一种非常优雅的方式。你可以这样做:

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

                                    【讨论】:

                                      【解决方案27】:

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

                                      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 函数。这种模式可以重复任意多次,为一个变量赋予几个不同的函数定义。

                                      【讨论】:

                                        【解决方案28】:

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

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

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

                                        清除(myDiv); // 清除 myDiv 引用的面板

                                        【讨论】:

                                          【解决方案29】:

                                          JavaScript 是无类型语言,我只认为在参数数量方面重载方法/函数才有意义。因此,我建议检查参数是否已定义:

                                          myFunction = function(a, b, c) {
                                               if (b === undefined && c === undefined ){
                                                    // do x...
                                               }
                                               else {
                                                    // do y...
                                               }
                                          };
                                          

                                          【讨论】:

                                          • 只是想注意,untyped 并不意味着“没有类型”。
                                          【解决方案30】:

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

                                          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
                                          

                                          【讨论】:

                                            猜你喜欢
                                            • 2018-10-13
                                            • 1970-01-01
                                            • 1970-01-01
                                            • 2011-04-14
                                            • 1970-01-01
                                            • 1970-01-01
                                            • 2014-01-09
                                            • 1970-01-01
                                            • 1970-01-01
                                            相关资源
                                            最近更新 更多