【问题标题】:How to overload functions in javascript?如何在javascript中重载函数?
【发布时间】:2012-06-07 00:38:59
【问题描述】:

经典(非 js)重载方法:

function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

Javascript 不允许使用相同的名称定义多个函数。因此,出现了这样的事情:

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

除了传递带有重载的对象之外,还有更好的解决方法来处理 javascript 中的函数重载吗?

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

【问题讨论】:

  • 那你是在想javascript错了;如果你有重载和范围的问题,你可能应该有一个不同的方法名称,因为它听起来像是在做另一份工作
  • @joshcomley - 重载和作用域需要代码来处理。我只是想确保我使用的代码尽可能高效。使用变通方法处理范围很好,但我更喜欢至少尝试使用最佳实践方法。

标签: javascript


【解决方案1】:

Javascript 中的参数重载有多个方面:

  1. 可变参数 - 您可以传递不同的参数集(类型和数量),函数将以与传递给它的参数相匹配的方式运行。

  2. 默认参数 - 如果参数未传递,您可以为其定义默认值。

  3. 命名参数 - 参数顺序变得无关紧要,您只需命名要传递给函数的参数。

下面是关于这些参数处理类别的部分。

变量参数

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

jQuery 一直都是这样做的。您可以使某些参数成为可选参数,也可以根据传递给它的参数在函数中进行分支。

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

  1. 您可以通过检查声明的参数名称值是否为 undefined 来检查是否存在任何给定参数。
  2. 您可以通过arguments.length查看总量或参数。
  3. 您可以检查任何给定参数的类型。
  4. 对于可变数量的参数,您可以使用arguments 伪数组通过arguments[i] 访问任何给定参数。

这里有一些例子:

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

obj.data("key");
obj.data("key", value);
obj.data();
obj.data(object);

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

以下是如何辨别所有这些英语选项的方法,然后我会将它们全部组合到代码中:

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

如果传递给.data() 的第一个参数是字符串,第二个参数是undefined,那么调用者必须使用这种形式。


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

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


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

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


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

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


以下是如何将所有这些组合到一组 javascript 逻辑中:

 // method declaration for .data()
 data: function(key, value) {
     if (arguments.length === 0) {
         // .data()
         // no args passed, return all keys/values in an object
     } else if (typeof key === "string") {
         // first arg is a string, look at type of second arg
         if (typeof value !== "undefined") {
             // .data("key", value)
             // set the value for a particular key
         } else {
             // .data("key")
             // retrieve a value for a key
         }
     } else if (typeof key === "object") {
         // .data(object)
         // set all key/value pairs from this object
     } else {
         // unsupported arguments passed
     }
 },

此技术的关键是确保您想要接受的所有形式的参数都是唯一可识别的,并且不会混淆调用者使用的形式。这通常需要适当地对参数进行排序,并确保参数的类型和位置具有足够的唯一性,以便您始终可以分辨出正在使用哪种形式。

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

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

您可以轻松地将第三个参数设为可选,并且您可以轻松检测到该条件,但您不能只将第二个参数设为可选,因为您无法判断调用者的意思是传递哪些参数,因为无法识别如果第二个参数是第二个参数,或者第二个参数被省略,那么第二个参数的位置实际上是第三个参数:

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

由于所有三个参数都是同一类型,因此您无法区分不同参数之间的区别,因此您不知道调用者的意图。使用这种调用方式,只有第三个参数可以是可选的。如果您想省略第二个参数,则必须将其作为 null(或其他可检测值)传递,您的代码会检测到:

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

这是一个可选参数的 jQuery 示例。这两个参数都是可选的,如果没有传递,则采用默认值:

clone: function( dataAndEvents, deepDataAndEvents ) {
    dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
    deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

    return this.map( function () {
        return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
    });
},

这是一个 jQuery 示例,其中可以缺少参数或三种不同类型中的任何一种,从而为您提供四种不同的重载:

html: function( value ) {
    if ( value === undefined ) {
        return this[0] && this[0].nodeType === 1 ?
            this[0].innerHTML.replace(rinlinejQuery, "") :
            null;

    // See if we can take a shortcut and just use innerHTML
    } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
        (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
        !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {

        value = value.replace(rxhtmlTag, "<$1></$2>");

        try {
            for ( var i = 0, l = this.length; i < l; i++ ) {
                // Remove element nodes and prevent memory leaks
                if ( this[i].nodeType === 1 ) {
                    jQuery.cleanData( this[i].getElementsByTagName("*") );
                    this[i].innerHTML = value;
                }
            }

        // If using innerHTML throws an exception, use the fallback method
        } catch(e) {
            this.empty().append( value );
        }

    } else if ( jQuery.isFunction( value ) ) {
        this.each(function(i){
            var self = jQuery( this );

            self.html( value.call(this, i, self.html()) );
        });

    } else {
        this.empty().append( value );
    }

    return this;
},

命名参数

其他语言(如 Python)允许传递命名参数作为仅传递一些参数并使参数独立于它们传入的顺序的一种方式。Javascript 不直接支持命名参数的特性。一种常用的设计模式是传递属性/值的映射。这可以通过传递具有属性和值的对象来完成,或者在 ES6 及更高版本中,您实际上可以传递 Map 对象本身。

这是一个简单的 ES5 示例:

jQuery 的$.ajax() 接受一种使用形式,您只需将单个参数传递给它,该参数是具有属性和值的常规Javascript 对象。您传递给它的哪些属性决定了将哪些参数/选项传递给 ajax 调用。有些可能是必需的,许多是可选的。由于它们是对象的属性,因此没有特定的顺序。实际上,可以在该对象上传递超过 30 种不同的属性,只需要一个(url)。

这是一个例子:

$.ajax({url: "http://www.example.com/somepath", data: myArgs, dataType: "json"}).then(function(result) {
    // process result here
});

$.ajax() 实现中,它可以只询问传入对象上传递了哪些属性,并将这些属性用作命名参数。这可以通过for (prop in obj) 或通过使用Object.keys(obj) 将所有属性放入一个数组然后迭代该数组来完成。

当有大量参数和/或许多参数是可选的时,这种技术在 Javascript 中非常常用。注意:这给实现函数带来了责任,以确保存在最小的有效参数集,并为调用者提供一些调试反馈,如果传递的参数不足(可能通过抛出带有有用错误消息的异常) .

在 ES6 环境中,可以使用解构为上述传递的对象创建默认属性/值。这在this reference article 中有更详细的讨论。

这是那篇文章中的一个例子:

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

然后,您可以这样称呼它:

selectEntries({start: 5});
selectEntries({start: 5, end: 10});
selectEntries({start: 5, end: 10, step: 2});
selectEntries({step: 3});
selectEntries();

您未在函数调用中列出的参数将从函数声明中获取它们的默认值。

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

函数参数的默认值

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

例如:

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

multiply(5); // 5

更多使用方式的描述here on MDN

【讨论】:

  • 所以我应该坚持使用自适应函数并接受它作为实现函数重载外观的最佳实践?
  • @TravisJ - 是的,自适应函数是在 Javascript 中完成重载的方式。您的另一个选择是使用具有不同参数的单独命名的函数。如果这两个实现没有任何共同点,那么任何一个都是可接受的做法。如果这两个实现本质上是做同样的事情,只是从不同的参数开始,那么我认为使用自适应函数会让你的界面更加紧凑,并且它让你有机会在两个相关的实现之间共享代码。
  • 添加了一些代码示例,用英文和实际代码解释。
  • 当我尝试resetProgressBar: function(display_errors, lockout = false) 时,我在最新版本的 Safari 上收到错误,意外的令牌 '='。
  • @Nick - 根据这个ES6 compatibility table,默认参数值应该适用于 Safari 10 及更高版本。根据this MDN page,它在 Safari 中表示“不支持”。我没有Mac,所以我无法自己测试。它似乎通常是后来在许多浏览器中实现的 ES6 功能之一。例如,它看起来不像是在 iOS9 中。
【解决方案2】:

在 JavaScript 中重载函数可以通过多种方式完成。所有这些都涉及一个主功能,该功能要么执行所有流程,要么委托给子功能/流程。

最常见的简单技术之一涉及简单的开关:

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

更优雅的技术是使用数组(或对象,如果您不为 every 参数计数进行重载):

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

前面的例子不是很优雅,任何人都可以修改fooArr,如果有人将两个以上的参数传递给foo,它会失败,所以更好的形式是使用模块模式和一些检查:

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

当然,您的重载可能希望使用动态数量的参数,因此您可以为 fns 集合使用一个对象。

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

我个人的偏好往往是switch,尽管它确实增加了主功能。我使用这种技术的一个常见例子是访问器/修改器方法:

function Foo() {} //constructor
Foo.prototype = {
    bar: function (val) {
        switch (arguments.length) {
        case 0:
            return this._bar;
        case 1:
            this._bar = val;
            return this;
        }
    }
}

【讨论】:

  • 这是一个很好的替代例子:)
  • 很好,我喜欢第二种技巧
【解决方案3】:

你不能做严格意义上的方法重载。不像 javac# 支持的方式。

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

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

假设你有方法

function foo(x)
{
} 

您可以定义一个新方法

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

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

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

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

(more details) PS:以上链接指向我的个人博客,其中包含更多详细信息。

【讨论】:

  • 这不是问题,这是一项功能。
【解决方案4】:

我根据参数编号使用了一些不同的重载方法。 不过我相信 John Fawcett 的方法也不错。 这里是示例,代码基于 John Resig(jQuery 的作者)的解释。

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

可用性:

var obj = {};
overload(obj, 'function_name', function(){ /* what we will do if no args passed? */});
overload(obj, 'function_name', function(first){ /* what we will do if 1 arg passed? */});
overload(obj, 'function_name', function(first, second){ /* what we will do if 2 args passed? */});
overload(obj, 'function_name', function(first,second,third){ /* what we will do if 3 args passed? */});
//... etc :)

【讨论】:

    【解决方案5】:

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

    var out = def({
        'int': function(a) {
            alert('Here is int '+a);
        },
    
        'float': function(a) {
            alert('Here is float '+a);
        },
    
        'string': function(a) {
            alert('Here is string '+a);
        },
    
        'int,string': function(a, b) {
            alert('Here is an int '+a+' and a string '+b);
        },
        'default': function(obj) {
            alert('Here is some other value '+ obj);
        }
    
    });
    
    out('ten');
    out(1);
    out(2, 'robot');
    out(2.5);
    out(true);
    

    用于实现此目的的方法:

    var def = function(functions, parent) {
     return function() {
        var types = [];
        var args = [];
        eachArg(arguments, function(i, elem) {
            args.push(elem);
            types.push(whatis(elem));
        });
        if(functions.hasOwnProperty(types.join())) {
            return functions[types.join()].apply(parent, args);
        } else {
            if (typeof functions === 'function')
                return functions.apply(parent, args);
            if (functions.hasOwnProperty('default'))
                return functions['default'].apply(parent, args);        
        }
      };
    };
    
    var eachArg = function(args, fn) {
     var i = 0;
     while (args.hasOwnProperty(i)) {
        if(fn !== undefined)
            fn(i, args[i]);
        i++;
     }
     return i-1;
    };
    
    var whatis = function(val) {
    
     if(val === undefined)
        return 'undefined';
     if(val === null)
        return 'null';
    
     var type = typeof val;
    
     if(type === 'object') {
        if(val.hasOwnProperty('length') && val.hasOwnProperty('push'))
            return 'array';
        if(val.hasOwnProperty('getDate') && val.hasOwnProperty('toLocaleTimeString'))
            return 'date';
        if(val.hasOwnProperty('toExponential'))
            type = 'number';
        if(val.hasOwnProperty('substring') && val.hasOwnProperty('length'))
            return 'string';
     }
    
     if(type === 'number') {
        if(val.toString().indexOf('.') > 0)
            return 'float';
        else
            return 'int';
     }
    
     return type;
    };
    

    【讨论】:

    • 一个处理输入的函数,非常聪明。
    【解决方案6】:

    在 javascript 中,您可以只实现一次函数并在没有参数myFunc() 的情况下调用该函数,然后检查选项是否为“未定义”

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

    【讨论】:

      【解决方案7】:

      https://github.com/jrf0110/leFunc

      var getItems = leFunc({
        "string": function(id){
          // Do something
        },
        "string,object": function(id, options){
          // Do something else
        },
        "string,object,function": function(id, options, callback){
          // Do something different
          callback();
        },
        "object,string,function": function(options, message, callback){
          // Do something ca-raaaaazzzy
          callback();
        }
      });
      
      getItems("123abc"); // Calls the first function - "string"
      getItems("123abc", {poop: true}); // Calls the second function - "string,object"
      getItems("123abc", {butt: true}, function(){}); // Calls the third function - "string,object,function"
      getItems({butt: true}, "What what?" function(){}); // Calls the fourth function - "object,string,function"
      

      【讨论】:

        【解决方案8】:

        JS重载没问题,pb函数重载时如何保持代码干净?

        您可以使用 a forward 来获得干净的代码,基于以下两点:

        1. 参数数量(调用函数时)。
        2. 参数类型(调用函数时)

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

        【讨论】:

          【解决方案9】:

          看看这个:

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

          基本上,在您的班级中,您可以为要重载的函数编号,然后通过一个函数调用添加函数重载,既快速又简单。

          【讨论】:

            【解决方案10】:

            由于 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 是一个关于如何使用选项对象的示例

            【讨论】:

              【解决方案11】:

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

              <script > 
              //Main function to add the methods
              function addMethod(object, name, fn) {
                var old = object[name];
                object[name] = function(){
                  if (fn.length == arguments.length)
                    return fn.apply(this, arguments)
                  else if (typeof old == 'function')
                    return old.apply(this, arguments);
                };
              }
              
              
                var ninjas = {
                 values: ["Dean Edwards", "Sam Stephenson", "Alex Russell"]
              };
              
              //Here we declare the first function with no arguments passed
                addMethod(ninjas, "find", function(){
                  return this.values;
              });
              
              //Second function with one argument
                addMethod(ninjas, "find", function(name){
                  var ret = [];
                  for (var i = 0; i < this.values.length; i++)
                    if (this.values[i].indexOf(name) == 0)
                      ret.push(this.values[i]);
                  return ret;
                });
              
              //Third function with two arguments
                addMethod(ninjas, "find", function(first, last){
                  var ret = [];
                  for (var i = 0; i < this.values.length; i++)
                    if (this.values[i] == (first + " " + last))
                      ret.push(this.values[i]);
                  return ret;
                });
              
              
              //Now you can do:
              ninjas.find();
              ninjas.find("Sam");
              ninjas.find("Dean", "Edwards")
              </script>
              

              【讨论】:

                【解决方案12】:

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

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

                【讨论】:

                  【解决方案13】:

                  您想要达到的目的最好使用函数的本地 arguments 变量。

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

                  您可以找到有关其工作原理的更好解释here

                  【讨论】:

                  • 那么碰撞检测呢?这是使用无参数函数或检查是否存在的好方法,但在重载方面并不是真正的最佳实践。
                  【解决方案14】:

                  如何使用扩展运算符作为参数?可以使用多个参数调用同一个块。所有的参数都被添加到一个数组中,在方法中你可以根据长度循环。

                      function mName(...opt){
                          console.log(opt); 
                      }
                      mName(1,2,3,4); //[1,2,3,4]
                      mName(1,2,3); //[1,2,3]
                  

                  【讨论】:

                    【解决方案15】:

                    (() => {
                      //array that store functions
                        var Funcs = []
                         /**
                         * @param {function} f overload function
                         * @param {string} fname overload function name
                       * @param {parameters} vtypes function parameters type descriptor (number,string,object....etc
                         */
                        overloadFunction = function(f, fname, ...vtypes) {
                            var k,l, n = false;
                            if (!Funcs.hasOwnProperty(fname)) Funcs[fname] = [];
                            Funcs[fname].push([f, vtypes?vtypes: 0 ]);
                            window[fname] = function() {
                                for (k = 0; k < Funcs[fname].length; k++)
                                    if (arguments.length == Funcs[fname][k][0].length) {
                                        n=true;
                                        if (Funcs[fname][k][1]!=0)
                                        for(i=0;i<arguments.length;i++)
                                        {
                                            if(typeof arguments[i]!=Funcs[fname][k][1][i])
                                            {
                                                n=false;
                                            }
                                        }
                                        if(n) return Funcs[fname][k][0].apply(this, arguments);
                                    }
                            }
                        }
                    })();
                    
                    //First sum function definition with parameter type descriptors
                    overloadFunction(function(a,b){return a+b},"sum","number","number")
                    //Second sum function definition with parameter with parameter type descriptors
                    overloadFunction(function(a,b){return a+" "+b},"sum","string","string")
                    //Third sum function definition (not need parameter type descriptors,because no other functions with the same number of parameters
                    overloadFunction(function(a,b,c){return a+b+c},"sum")
                    
                    //call first function
                    console.log(sum(4,2));//return 6
                    //call second function
                    console.log(sum("4","2"));//return "4 2"
                    //call third function
                    console.log(sum(3,2,5));//return 10
                    //ETC...

                    【讨论】:

                      猜你喜欢
                      • 2012-04-03
                      • 1970-01-01
                      • 2016-11-09
                      • 1970-01-01
                      • 1970-01-01
                      • 2019-09-13
                      • 2011-11-19
                      • 2021-03-14
                      相关资源
                      最近更新 更多