如何在javascript中重载函数?

古典(非js)重载方法:

function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

JavaScript不会让多个函数被定义为相同的名称。 因此,这样的事情显示出来:

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

是否有一个更好的解决方法,在JavaScript中的函数重载,而不是传递一个超载的对象?

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


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

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

  • 默认参数 - 您可以为参数定义一个默认值,如果它没有通过。

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

  • 以下是有关这些参数处理类别的部分。

    变量参数

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

    jQuery一直都在这样做。 你可以使一些参数是可选的,或者你可以在你的函数中进行分支,这取决于传递给它的参数。

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

  • 您可以通过检查声明的参数名称值是否undefined来检查是否存在任何给定的参数。
  • 您可以使用arguments.length检查总数量或arguments.length
  • 您可以检查任何给定参数的类型。
  • 对于可变数量的参数,可以使用arguments pseudo-array来访问任何具有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环境中,可以使用解构来为上面传递的对象创建默认属性/值。 这在这篇参考文章中有更详细的讨论。

    这篇文章中有一个例子:

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

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

    函数参数的默认值

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

    例如:

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

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


    用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 ,如果有人向foo传入2个以上的参数,它会失败,所以更好的形式是使用模块模式和一些检查:

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

    我正在使用基于参数数量的一些不同的重载方法。 不过我相信约翰福西特的做法也不错。 这里的例子是基于John Resig(jQuery's Author)解释的代码。

    // 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 :)
    
    链接地址: http://www.djcxy.com/p/51977.html

    上一篇: How to overload functions in javascript?

    下一篇: Function Overloading on prototype Javascript?