如何在javascript中重载函数?

新手上路,请多包涵

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

 function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

Javascript 不会让多个函数被定义为同一个名字。因此,出现这样的事情:

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

除了传递其中包含重载的对象之外,对于 javascript 中的函数重载是否有更好的解决方法?

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

原文由 Travis J 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 461
2 个回答

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

您可以轻松地将第三个参数设置为可选,并且可以轻松检测到该条件,但是您不能仅将第二个参数设置为可选,因为您无法判断调用者要传递的参数中的哪一个,因为无法确定第二个参数是否为argument 应该是第二个参数,或者第二个参数被省略了,所以第二个参数的位置实际上是第三个参数:

 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 环境中,可以使用解构为上面传递的对象创建默认属性/值。 这篇参考文章对此 进行了更详细的讨论。

这是该文章中的一个示例:

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

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

This creates default properties and values for the start , end and step properties on an object passed to the selectEntries() function.

函数参数的默认值

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

例如:

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

multiply(5); // 5

在 MDN 上 进一步描述了这可以使用的方式。

原文由 jfriend00 发布,翻译遵循 CC BY-SA 4.0 许可协议

可以通过多种方式重载 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;
    }
}

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

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

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

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

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

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


我个人的偏好倾向于 switch ,尽管它确实增加了 master 功能。我将在何处使用此技术的一个常见示例是访问器/修改器方法:

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

原文由 zzzzBov 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏