Javascript函数重载,存在呢—还是存在呢?
2016-06-17 10:25
381 查看
1.What'sis函数重载?
Functionormethodoverloadingisdeclaringfunctionswiththesamenamethatacceptdifferentargumentsthushavedifferentbehaviorsdependingonpassedarguments.函数重载(方法重载)是指声明一组具有不同数量或者不同类型的参数的同名函数,这些同名函数会根据参数的不同表现出不同的行为。(翻译的竟然词不达意,罪过罪过)
2.静态语言的重载
像Java、C++这样的静态语言具有天然的函数重载特性,以C++为例,你一定理解下面的代码。#include<iostream>
usingnamespacestd;
voidprint(inti){
cout<<"Hereisnum"<<i<<endl;
}
voidprint(stringstr){
cout<<"Hereisstring"<<str<<endl;
}
intmain(){
print(10);//Hereisint10
print("ten");//Hereisstringten
}
[/code]
可以发现在C++中会根据参数的类型自动选择合适的函数执行,如果把上面的代码改造成Javascript代码如下:
functionprint(i){
console.log("Hereisnum"+i);
}
functionprint(str){
console.log("Hereisstring"+str);
}
print(100);//Hereisstring100
print("ten");//Hereisstringten
[/code]
显然,这个例子中声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数。那Javascript中到底有没有函数重载特性,
正确答案是:绝逼没有,但是可以模拟重载的行为
在《JS高程》中有这样一段总结:Javascript函数不能像传统意义上那样实现重载。而在其他语言(如Java、C++)中,可以为一个函数编写两个定义,只要这两个定义的签名(接受的参数的类型和数量)不同即可。但是Javascript函数没有签名,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的。如果在Javascript中定义了两个名字相同的函数,则该名字只属于后定义的函数。也就是说:虽然Javascript不能实现真正的函数重载特性,但是通过检查传入函数中的参数的类型和数量并作出不同的反应,也能模仿方法的重载(想想前面的关于函数重载的定义,重载的结果是同名函数会根据参数的不同表现出不同的行为,既然不能变成真正的“鸭子”,但是能模仿“鸭子”叫,我们就姑且把它看作是“鸭子”了)。[/b]
3.如何实现具有Javascript特色的函数重载
StackOverflow上有人总结了一下几种方法:Usingdifferentnamesinthefirstplace(使用不同的函数名)functionprintNum(num){
console.log("Hereisnum"+num);
}
functionprintString(str){
console.log("Hereisstring"+str);
}
printNum(100);//Hereisnum100
printString("ten");//Hereisstringten
[/code]上述这种做法实际上已经违背了函数重载的基本特性,因为已经出现了不同的函数名。Usingoptionalargumentslike
y=y||'default'(检测是否提供实参,否则采用默认值)
举例说明,现在我们要同时打印一个数字和一个字符串
functionprint(num,str){
varinner_num=num||0;
varinner_str=str||"default";
console.log("Hereisnum"+inner_num);
console.log("Hereisstring"+inner_str);
}
print();
//Hereisnum0
//Hereisstringdefault
print(10);
//Hereisnum10
//Hereisstringdefault
print(10,"ten");
//Hereisnum10
//Hereisstringten
[/code]使用这种方法的结果也是显而易见的,实现起来也是相当简单,在Javascript程序中被大量采用,但是执行print(,"ten");这样的操作会发生错误,在某些情况下如果0/null/undefined为有效值时,执行y=y||'default'会不分青红皂白的把这些值过滤掉。Usingnumberofarguments(根据参数数量来实现重载)
functionprint(num,str){
varlen=arguments.length;
if(len===1){
console.log("Hereisnum"+num);
}
elseif(len===2){
console.log("Hereisnum"+num);
console.log("Hereisstring"+str);
}
else{
console.log("看....,飞碟!");
}
}
print();//看....,飞碟!
print(10);//Hereisnum10
print(10,"ten");
//Hereisnum10
//Hereisstringten
[/code]从上面的结果看,如果出入的参数只有一个字符串,仍然无法打印。
Checkingtypesofarguments(根据参数类型来实现重载)
functionprint(num,str){
if(typeofnum=="number"){
console.log("Hereisnum"+num);
}
if(typeofstr=="string"){
console.log("Hereisstring"+str);
}
}
print();//无输出
print(10);//Hereisnum10
print('',"ten");//Hereisstringten
print(10,"ten");
//Hereisnum10
//Hereisstringten
[/code]这种方法的结果算是比较满足函数重载的行为了,但是有没有发现如果只想打印字符串时,也不得不传入两个参数,并且要求开发人员知道函数内部能过滤掉第一个参数,同时检测参数数量和参数类型可以解决这个问题。
下面的例子中直接借用了arguments数组
functionprint(num,str){
varlen=arguments.length;
if((len===1)&&(typeofarguments[0]==="number")){
console.log("Hereisnum"+arguments[0]);
}
elseif((len===1)&&(typeofarguments[0]==="string")){
console.log("Hereisstring"+arguments[0]);
}
elseif((len===2)&&(typeofarguments[0]==="number")&&(typeofarguments[1]==="string")){
console.log("Hereisnum"+arguments[0]);
console.log("Hereisstring"+arguments[1]);
}
else{
console.log("看....,飞碟!");
}
}
print();//看....,飞碟!
print(10);//Hereisnum10
print("ten");//Hereisstringten
print(10,"ten");
//Hereisnum10
//Hereisstringten
[/code]
在预期参数较多的情况下,上述的所有方法可能都不尽如人意,检测参数类型会拖慢执行速度,并且并且需要检测的类型过多,Arrays,nulls,Objects,etc.而且如果有几个参数的类型相同呢,那岂不是容易混淆,这时Javascript中有一种代码风格被广泛使用:使用对象作为函数的最后一个参数,对象可以承载任何类型的键值对。
functionfoo(a,b,opts){
}
foo(1,2,{"method":"add"});
foo(3,4,{"test":"equals","bar":"tree"});
[/code]在代码中只需要检测对象的某个键值是否存在来做相应处理。
至此,前面模拟函数重载的方法的基本思想都是基于传递的参数定义一个有很多不同功能的函数,通过使用if-then或者switch子句处理不同的行为,但是一旦事情开始变得的复杂,大量的分支语句就会导致代码笨拙,JohnResig在《SecretsoftheJavaScriptNinja》(中译名:JavaScript忍者秘籍)中提到一种基于函数的length属性的重载方法ECMAScript中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:length。其中,length属性表示函数希望接收的命名参数的个数,如下面的例子所示(见《Js高程P116页》)。
functionsayName(name){
alert(name);
}
functionsum(num1,num2){
returnnum1+num2;
}
functionsayHi(){
alert("hi");
}
alert(sayName.length);//1
alert(sum.length);//2
alert(sayHi.length);//0
[/code]因此,对于一个函数,在参数方面,我们可以确定两件事
■通过其length属性,可以知道声明了多少命名参数
■通过arguments.length,可以知道在调用时传入了多少参数
下面介绍一下大牛是如何实现借助这个特性实现函数的重载的
varninja={};
addMethod(ninja,'whatever',function(){/*dosomething*/});
addMethod(ninja,'whatever',function(a){/*dosomethingelse*/});
addMethod(ninja,'whatever',function(a,b){/*yetsomethingelse*/});
[/code]在这里,先创建一个对象,然后使用同样的名称(whatever)将方法添加到该对象上,只不过每个重载的函数都是单独的,注意每个重载的参数个数都不相同。通过这种方式,真正为每个重载都创建了一个独立的匿名函数。漂亮且整洁!现在的关键就是实现addMethod()函数,大牛的实现相当巧妙:
functionaddMethod(object,name,fn){
varold=object[name];//保存原有的函数,因为调用的时候可能不匹配传入的参数个数
object[name]=function(){//重写了object[name]的方法,是一个匿名函数
//如果该匿名函数的形参个数和实参个数匹配,就调用该函数
if(fn.length===arguments.length){
returnfn.apply(this,arguments);
//如果传入的参数不匹配,就调用原有的参数
}elseif(typeofold==="function"){
returnold.apply(this,arguments);
}
}
}
[/code]现在,我们一起来分析一个这个addMethod函数,它接收3个参数:第一个为要绑定方法的对象,
第二个为绑定方法所用的属性名称,
第三个为需要绑定的方法(一个匿名函数)
好吧,一言不合就举个例子吧
//addMethod
functionaddMethod(object,name,fn){
varold=object[name];
object[name]=function(){
if(fn.length===arguments.length){
returnfn.apply(this,arguments);
}elseif(typeofold==="function"){
returnold.apply(this,arguments);
}
}
}
varprinter={};
//不传参数时,打印“飞碟”
addMethod(printer,"print",function(){
console.log("看,飞碟!");
});
//传一个参数时,打印数字
addMethod(printer,"print",function(num){
console.log("Hereisnum"+num);
});
//传两个参数时,打印数字和字符串
addMethod(printer,"print",function(num,str){
console.log("Hereisnum"+num);
console.log("Hereisstring"+str);
});
//测试:
printer.print();//看,飞碟!
printer.print(10);//Hereisnum10
printer.print(10,"ten");
//Hereisnum10
//Hereisstringten
[/code]对上面的思想稍微改造一下,我们可以直接把重载功能放在函数对象原型上。
//Function.prototype.overload-By沐浴星光
Function.prototype.overload=function(fn){
varold=this;
returnfunction(){
if(fn.length===arguments.length){
returnfn.apply(this,arguments);
}else{
returnold.apply(this,arguments);
}
}
}
//不传参数时,打印“飞碟”
functionprintNone(){
console.log("看,飞碟!");
}
//传一个参数时,打印数字
functionprintInt(num){
console.log("Hereisnum"+num);
}
//传两个参数时,打印数字和字符串
functionprintBoth(num,str){
console.log("Hereisnum"+num);
console.log("Hereisstring"+str);
}
print=function(){};
print=print.overload(printNone).overload(printInt).overload(printBoth);
print();//看,飞碟!
print(10);//Hereisnum10
print(10,"ten");
//Hereisnum10
//Hereisstringten
[/code]
参考:
相关文章推荐
- jascript base64编解码,好东西
- 【VisualStudioCode】VSCode隐藏文件夹ignore folder
- js盒子模型常用属性
- JSON.stringify语法解析(自己留存)
- 2016.06.17廖雪峰JS__学习笔记(操作DOM)__P12
- Ionic Js七:手势事件
- 在js文件中动态设置class方法
- JS常用字符串方法(推荐)
- ExtJs教程----解决ExtJS 5.1.0.107在IE浏览器下面,当页面一打开的时候,下拉框里面的值会全都显示在页面上而且会现出错位
- js之数组常见的方法
- javascript多重继承
- JavaScript DOM中获取元素节点的父节点和父节点名
- JavaScript DOM中获取元素节点的父节点和父节点名
- JavaScript DOM中获取元素节点的父节点和父节点名
- JavaScript DOM中获取元素节点的父节点和父节点名
- JavaScript DOM中获取元素节点的父节点和父节点名
- JavaScript DOM中获取元素节点的父节点和父节点名
- JavaScript DOM中获取元素节点的父节点和父节点名
- JavaScript DOM中获取元素节点的父节点和父节点名
- JavaScript DOM中获取元素节点的父节点和父节点名