在Javascript中伪造函数重载的最佳方法是什么?
我知道不可能像在其他语言中一样重载Javascript中的函数.如果我需要一个函数有两个用途foo(x)
和foo(x,y,z)
这是最好的/优选的方法:
首先使用不同的名称
使用可选参数 y = y || 'default'
使用参数数量
检查参数的类型
或者怎么样?
epascarello.. 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"});
然后你可以在你的方法中处理它.[切换,if-else等]
使用参数进行函数重载的最佳方法是不检查参数长度或类型; 检查类型只会让你的代码变慢,你可以享受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"});
然后你可以在你的方法中处理它.[切换,if-else等]
我经常这样做:
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
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
这个特殊的例子在javascript中实际上比C#更优雅.未指定的参数在javascript中为'undefined',在if语句中计算结果为false.但是,函数定义不传达p2和p3是可选的信息.如果你需要大量的重载,jQuery决定使用一个对象作为参数,例如,jQuery.ajax(options).我同意他们这是最强大且可清晰记录的重载方法,但我很少需要一个或两个以上的快速可选参数.
编辑:根据伊恩的建议改变了IF测试
JavaScript中没有真正的函数重载,因为它允许传递任何类型的任意数量的参数.您必须在函数内部检查已传递的参数数量以及它们的类型.
正确的答案是在JAVASCRIPT中没有超载.
检查/切换功能内部不是OVERLOADING.
重载的概念:在一些编程语言中,函数重载或方法重载是能够使用不同的实现创建多个同名方法.对重载函数的调用将运行适合于调用上下文的该函数的特定实现,允许一个函数调用根据上下文执行不同的任务.
例如,doTask()和doTask(对象O)是重载方法.要调用后者,必须将对象作为参数传递,而前者不需要参数,并使用空参数字段调用.常见的错误是在第二种方法中为对象分配一个默认值,这会导致一个模糊的调用错误,因为编译器不知道要使用哪两种方法.
https://en.wikipedia.org/wiki/Function_overloading
所有建议的实现都很棒,但事实上,没有JavaScript的原生实现.
有两种方法可以更好地解决这个问题:
如果要保留很多灵活性,请传递字典(关联数组)
以对象作为参数并使用基于原型的继承来增加灵活性.
这是一种允许使用参数类型进行实际方法重载的方法,如下所示:
Func(new Point()); Func(new Dimension()); Func(new Dimension(), new Point()); Func(0, 0, 0, 0);
编辑(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 arg
和arg.hasOwnProperty('param')
,和函数重载比传递对象和属性检查(在这个基准测试至少)快得多.
从设计角度来看,如果重载参数对应于同一个动作,则函数重载仅有效或逻辑.因此,有理由认为应该有一个仅涉及具体细节的基础方法,否则可能表明不适当的设计选择.因此,人们还可以通过将数据转换为相应的对象来解决函数重载的使用问题.当然,必须考虑问题的范围,因为如果你的意图只是打印一个名字,就不需要精心设计,但是对于框架和库的设计,这种思想是合理的.
我的例子来自Rectangle实现 - 因此提到了Dimension和Point.也许矩形可以添加一个GetRectangle()
方法到Dimension
和Point
原型,然后超载问题的功能进行排序.原始人怎么样?好吧,我们有参数长度,现在是一个有效的测试,因为对象有一个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);
最好的方法实际上取决于函数和参数.在不同情况下,您的每个选项都是个好主意.我通常按以下顺序尝试这些,直到其中一个工作:
使用可选参数,例如y = y || '默认'.如果你能做到这一点很方便,但它可能并不总是有效,例如当0/null/undefined是一个有效的参数时.
使用参数数量.与最后一个选项类似,但在#1不起作用时可能有效.
检查参数的类型.这可以在参数数量相同的某些情况下起作用.如果无法可靠地确定类型,则可能需要使用不同的名称.
首先使用不同的名称.如果其他选项不起作用,不实用或与其他相关功能保持一致,则可能需要执行此操作.
如果我需要一个有两个使用foo(x)和foo(x,y,z)的函数,这是最好/首选的方法吗?
问题是JavaScript本身不支持方法重载.因此,如果它看到/解析两个或多个具有相同名称的函数,它将只考虑最后定义的函数并覆盖之前的函数.
我认为适合大多数情况的方式之一是 -
让我们说你有方法
function foo(x) { }
您可以定义一个新方法,而不是在javascript中无法实现的重载方法
fooNew(x,y,z) { }
然后修改第一个函数如下 -
function foo(arguments) { if(arguments.length==2) { return fooNew(arguments[0], arguments[1]); } }
如果您有许多这样的重载方法,请考虑使用switch
而不仅仅是if-else
语句.
(更多细节)
PS:以上链接转到我的个人博客,其中包含其他详细信息.
我不确定最佳实践,但这是我如何做到的:
/* * 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"
我刚试过这个,也许它适合你的需要.根据参数的数量,您可以访问其他功能.您在第一次调用时初始化它.并且函数映射隐藏在闭包中.
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; };
测试一下.
TEST.multiFn("0") .multiFn() .multiFn("0","1","2");
由于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 + "}"; }
这是一个关于如何使用options对象的示例