Javascript中的函数重载 - 最佳实践

ham*_*guz 739 javascript overloading

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

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

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

epa*_*llo 568

使用参数进行函数重载的最佳方法是不检查参数长度或类型; 检查类型只会让你的代码变慢,你可以享受Arrays,nulls,Objects等的乐趣.

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

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

然后你可以在你的方法中处理它.[切换,if-else等]

  • 这不是函数重载.函数重载具有两个具有相同名称但不同参数的独立函数.你所描述的只是一个带有对象参数的函数. (72认同)
  • @ user1334007不可能像在Java/.NET中那样进行函数重载.是的,这不是"完全"超载,但它完成了工作. (61认同)
  • 你能提供一个foo()的示例实现来说明如何使用/引用这些"opts"参数吗? (43认同)
  • 萌//这可能是这样的; `if(opts ['test'])//如果测试参数存在,做一些事情..如果(opts ['bar'])//如果bar param存在,做一些事情 (24认同)
  • 我很惊讶没有人问过这个问题:为什么不建议检查`arguments.length`?此外,我以前来过这里并阅读_大多数开发人员做的是......,但我确信这是我见过的唯一一个完成的地方.这种方法也破坏了"重载"的语法含糖! (16认同)
  • "检查类型只会使你的代码变慢"相对而言,创建一个对象然后查找属性远比类型检查慢.如果传递的对象已经存在,那么对它的属性查找很可能会更快.http://jsperf.com/type-check-vs-property-lookup本回答中的代码为每个调用创建一个新对象,该对象本身比类型检查慢. (7认同)
  • @ c24w是的,你可以查看lengthm,但它会因(obj,str,str)vs(obj,str,bool)等原因而失败. (6认同)
  • @Dorian使用选项对象是大量JS库的作用 - 你的(咳)选项是有限的. (5认同)
  • @Dorian你不能评论或使用JSDoc? (2认同)
  • 一种技术(将 ** 故意** 除非在不受支持的类型上)是使用 typeof 参数作为键,在具有类型特定函数的对象周围进行薄包装。例如`var thing_handlers={ string : function(x){ return x;}, number : function(x) {return x+25; } }; function do_thing(x) {return thing_handlers[typeof x](x);}` 这既快速又多态,最重要的是,在运行时崩溃以迫使开发人员编写安全(r)代码。 (2认同)

roc*_*ast 162

我经常这样做:

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

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

这个特殊的例子在javascript中实际上比C#更优雅.未指定的参数在javascript中为'undefined',在if语句中计算结果为false.但是,函数定义不传达p2和p3是可选的信息.如果你需要大量的重载,jQuery决定使用一个对象作为参数,例如,jQuery.ajax(options).我同意他们这是最强大且可清晰记录的重载方法,但我很少需要一个或两个以上的快速可选参数.

编辑:根据伊恩的建议改变了IF测试

  • 未指定的参数在JS中是"undefined",而不是"null".作为一种最佳实践,你永远不应该将任何东西设置为"undefined",所以只要你将测试改为`p2 === undefined`就不会有问题. (15认同)
  • 我喜欢这样做:p3 = p3 || '默认值'; (7认同)
  • 简单来说,你的`typeof p2 ==="undefined"`可能与你在你的例子中所期望的相反,我认为`typeof p2!=="undefined"`就是你想要的.另外,我建议你应该连接字符串,数字和布尔值,你实际上做的是`p2 ==="number"; p3 ==="boolean"` (5认同)
  • 如果你传递`false`作为最后一个参数,那么它就不会将``false``连接到结尾,因为`if(p3)`不会分支. (3认同)
  • `===`和`!==`是什么意思?为什么不使用`==`和`!=`? (3认同)

Gum*_*mbo 72

JavaScript中没有真正的函数重载,因为它允许传递任何类型的任意数量的参数.您必须在函数内部检查已传递的参数数量以及它们的类型.

  • John Resig的功能超载http://ejohn.org/blog/javascript-method-overloading/ (13认同)
  • John Resig(jQuery 的)曾经尝试过这一点,但这种尝试纯粹是学术性的,并没有带来任何实际的好处。 (2认同)

Mar*_*rco 57

正确的答案是在JAVASCRIPT中没有超载.

检查/切换功能内部不是OVERLOADING.

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

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

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

所有建议的实现都很棒,但事实上,没有JavaScript的原生实现.

  • OP 要求一种“假”超载的方法。 (17认同)
  • 终于有正常答案了!JavaScript 中没有重载。 (6认同)
  • 当你应该关注预期含义时与人争论字面含义,就像当编译器只响应代码的字面含义时与编译器争论你的预期含义一样。OP 的目的显然是在调用站点之间实现与重载类似的效果,同时承认 JS 不支持该构造。 (6认同)
  • 正如我之前所说,我们在这里教育人们,我们不只是在不验证他们所问的问题是否正确的情况下给他们答案。 (3认同)

t3r*_*rse 26

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

  1. 如果要保留很多灵活性,请传递字典(关联数组)

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


Kel*_*yne 19

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

Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);
Run Code Online (Sandbox Code Playgroud)

编辑(2018):由于这是在2011年编写的,直接方法调用的速度大大增加,而重载方法的速度则没有.

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


以下是不同方法的基准 - https://jsperf.com/function-overloading.它表明,从16.0(测试版)开始,谷歌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.也许矩形可以添加一个GetRectangle()方法到DimensionPoint原型,然后超载问题的功能进行排序.原始人怎么样?好吧,我们有参数长度,现在是一个有效的测试,因为对象有一个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);
Run Code Online (Sandbox Code Playgroud)


Mat*_*ley 16

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

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

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

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

  4. 首先使用不同的名称.如果其他选项不起作用,不实用或与其他相关功能保持一致,则可能需要执行此操作.


Ani*_*kur 14

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

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

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

让我们说你有方法

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

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

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

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

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

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

(更多细节)

PS:以上链接转到我的个人博客,其中包含其他详细信息.


Luc*_*oli 13

并不是每个人都知道可以直接在函数签名中进行解构赋值。

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

例子:

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

myFunction({a: 1, b: 2});
myFunction({a: 1, b: 2, c: 3});

Run Code Online (Sandbox Code Playgroud)

您甚至不需要遵守参数的顺序,并且调用语句和目标方法签名之间存在命名一致性。

您还可以指定默认值

const myFunction = (({a = 1, b = 2, c} = {}) => {
    console.log(a, b, c);
});
Run Code Online (Sandbox Code Playgroud)


che*_*web 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"
Run Code Online (Sandbox Code Playgroud)

  • @Luis:我添加了一些有希望的有用评论. (2认同)

Ant*_*anK 6

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

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

测试一下.

TEST.multiFn("0")
    .multiFn()
    .multiFn("0","1","2");
Run Code Online (Sandbox Code Playgroud)


Vla*_*den 5

由于JavaScript没有函数重载选项,因此可以使用对象.如果有一个或两个必需参数,最好将它们与options对象分开.下面是一个示例,说明如果在options对象中未传递value,如何将options对象和填充值用于默认值.

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

}
Run Code Online (Sandbox Code Playgroud)

是一个关于如何使用options对象的示例


Sag*_*jal 5

介绍

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

Function overloading Definition, Function Length property,Function argument property

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

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

注意 - JS 不提供内置的函数重载能力。

选择

John E Resig(JS 的创建者)已经指出了一个替代方案,它使用上述先决条件来实现实现函数重载的能力。

下面的代码通过 using if-elseorswitch语句使用了一种简单但朴素的方法。

  • 评估argument-length财产。
  • 不同的值导致调用不同的函数。

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

另一种技术更加干净和动态。这种技术的亮点是addMethod泛型函数。

  • 我们定义了一个函数addMethod,用于向同名功能不同的对象添加不同的功能

  • addMethod函数下面接受三个参数对象名object、函数名name和我们要调用的函数fn

  • 内部addMethod定义var oldfunction闭包的帮助下存储对先前存储的引用- 一个保护性气泡。

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

  • 使用调试器了解代码流。
  • 下面addMethod添加了三个函数,当使用ninja.whatever(x)参数的数量调用时,这些函数x可以是任何参数,即空白或一个或多个,调用在使用该addMethod函数时定义的不同函数。

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