如何在javascript中重载函数?

Tra*_*s J 94 javascript

经典(非js)重载方法:

function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}
Run Code Online (Sandbox Code Playgroud)

Javascript不会让同一个名称定义多个函数.因此,这样的事情出现了:

function myFunc(options){
 if(options["overloaded"]){
  //code
 }
}
Run Code Online (Sandbox Code Playgroud)

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

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

jfr*_*d00 116

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);
Run Code Online (Sandbox Code Playgroud)

每一个触发一个不同的行为,并且不使用这种动态形式的重载,将需要四个单独的功能.

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

// get the data element associated with a particular key value
obj.data("key");
Run Code Online (Sandbox Code Playgroud)

如果传递给的第一个参数.data()是一个字符串而第二个参数是undefined,则调用者必须使用此表单.


// set the value associated with a particular key
obj.data("key", value);
Run Code Online (Sandbox Code Playgroud)

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


// get all keys/values
obj.data();
Run Code Online (Sandbox Code Playgroud)

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


// set all keys/values from the passed in object
obj.data(object);
Run Code Online (Sandbox Code Playgroud)

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


以下是如何在一组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
     }
 },
Run Code Online (Sandbox Code Playgroud)

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

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

obj.query("firstArg", "secondArg", "thirdArg");
Run Code Online (Sandbox Code Playgroud)

您可以轻松地使第三个参数可选,您可以轻松地检测到该条件,但您不能只使第二个参数可选,因为您无法分辨调用者的哪一个意味着要传递,因为无法确定是否第二个参数意味着是第二个参数或第二个参数被省略,所以第二个参数的位置实际上是第三个参数:

obj.query("firstArg", "secondArg");
obj.query("firstArg", "thirdArg");
Run Code Online (Sandbox Code Playgroud)

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

obj.query("firstArg", null, "thirdArg");
Run Code Online (Sandbox Code Playgroud)

这是一个可选参数的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 );
    });
},
Run Code Online (Sandbox Code Playgroud)

这是一个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;
},
Run Code Online (Sandbox Code Playgroud)

命名参数

其他语言(如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
});
Run Code Online (Sandbox Code Playgroud)

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

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

在ES6环境中,可以使用解构来为上面传递的对象创建默认属性/值.这篇参考文章将对此进行更详细的讨论.

这是该文章的一个例子:

function selectEntries({ start=0, end=-1, step=1 } = {}) {
    ···
};
Run Code Online (Sandbox Code Playgroud)

这将创建默认属性和值start,endstep传递到一个对象的属性selectEntries()的功能.

函数参数的默认值

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

例如:

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

multiply(5); // 5
Run Code Online (Sandbox Code Playgroud)

此处可以在MDN上使用方法的进一步说明.

  • @TravisJ - 是的,自适应函数是在Javascript中完成重载的方式.您的另一个选择是使用具有不同参数的单独命名的函数.如果这两种实现没有任何共同之处,那么任何一种都是可以接受的做法.如果这两个实现基本上做同样的事情,但只是从不同的参数开始,那么我认为它使你的界面更紧凑使用自适应功能,它让你有机会在两个相关的实现之间共享代码. (2认同)

zzz*_*Bov 33

在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

更优雅的技术是使用数组(或对象,如果你没有为每个参数计数重载):

fooArr = [
    function () {
    },
    function (a) {
    },
    function (a,b) {
    }
];
function foo(a, b) {
    return fooArr[arguments.length](a, b);
}
Run Code Online (Sandbox Code Playgroud)

前面的例子不是很优雅,任何人都可以修改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;
}());
Run Code Online (Sandbox Code Playgroud)

当然,您的重载可能需要使用动态数量的参数,因此您可以使用对象进行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;
}());
Run Code Online (Sandbox Code Playgroud)

我的个人偏好往往是switch,虽然它确实增加了主功能.我使用这种技术的一个常见例子是accessor/mutator方法:

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


Ani*_*kur 7

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

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

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

让我们说你有方法

function foo(x)
{
} 
Run Code Online (Sandbox Code Playgroud)

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

fooNew(x,y,z)
{
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

(更多细节)PS:以上链接转到我的个人博客,其中包含有关此内容的其他详细信息.


Val*_*usk 5

我根据参数数量使用了一些不同的重载方法。不过我相信 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);
            }
        };
    }
Run Code Online (Sandbox Code Playgroud)

可用性:

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 :)
Run Code Online (Sandbox Code Playgroud)


sta*_*mat 5

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

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);
Run Code Online (Sandbox Code Playgroud)

用于实现此目的的方法:

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;
};
Run Code Online (Sandbox Code Playgroud)

  • 一个处理输入的函数,非常聪明。 (2认同)