1.What's is 函数重载?

 Function or method overloading is declaring functions with the same name that accept different arguments thus have different behaviors depending on passed arguments.
函数重载(方法重载)是指声明一组具有不同数量或者不同类型的参数的同名函数,这些同名函数会根据参数的不同表现出不同的行为。(翻译的竟然词不达意,罪过罪过)

2.静态语言的重载

像Java、C++这样的静态语言具有天然的函数重载特性,以C++为例,你一定理解下面的代码。
  1. #include <iostream>
  2. using namespace std;
  3. void print(int i) {
  4. cout <<"Here is num " << i << endl;
  5. }
  6. void print(string str) {
  7. cout << "Here is string " <<str<< endl;
  8. }
  9. int main() {
  10. print(10);//Here is int 10
  11. print("ten");//Here is string ten
  12. }

可以发现在C++中会根据参数的类型自动选择合适的函数执行,如果把上面的代码改造成Javascript代码如下:
  1. function print(i) {
  2. console.log("Here is num"+i);
  3. }
  4. function print(str) {
  5. console.log("Here is string "+str);
  6. }
  7. print(100);//Here is string 100
  8. print("ten");//Here is string ten

显然,这个例子中声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数。那Javascript中到底有没有函数重载特性,
 
 
正确答案是:绝逼没有,但是可以模拟重载的行为

 
在《JS高程》中有这样一段总结:Javascript函数不能像传统意义上那样实现重载。而在其他语言(如 Java、C++)中,可以为一个函数编写两个定义,只要这两个定义的签名(接受的参数的类型和数量)不同即可。但是Javascript函数没有签名,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的。如果在 Javascript中定义了两个名字相同的函数,则该名字只属于后定义的函数。
也就是说:虽然Javascript不能实现真正的函数重载特性,但是通过检查传入函数中的参数的类型和数量并作出不同的反应,也能模仿方法的重载(想想前面的关于函数重载的定义,重载的结果是同名函数会根据参数的不同表现出不同的行为,既然不能变成真正的“鸭子”,但是能模仿“鸭子”叫,我们就姑且把它看作是“鸭子”了)。

3.如何实现具有Javascript特色的函数重载

Stack Overflow上有人总结了一下几种方法:
  • Using different names in the first place(使用不同的函数名)
  1. function printNum(num) {
  2. console.log("Here is num "+num);
  3. }
  4. function printString(str) {
  5. console.log("Here is string "+str);
  6. }
  7. printNum(100);//Here is num 100
  8. printString("ten");//Here is string ten
上述这种做法实际上已经违背了函数重载的基本特性,因为已经出现了不同的函数名。
  • Using optional arguments like y = y || 'default'(检测是否提供实参,否则采用默认值)

举例说明,现在我们要同时打印一个数字和一个字符串

  1. function print(num,str){
  2. var inner_num=num||0;
  3. var inner_str=str||"default";
  4. console.log("Here is num "+inner_num);
  5. console.log("Here is string "+inner_str);
  6. }
  7. print();
  8. //Here is num 0
  9. //Here is string default
  10. print(10);
  11. //Here is num 10
  12. //Here is string default
  13. print(10,"ten");
  14. //Here is num 10
  15. //Here is string ten
使用这种方法的结果也是显而易见的,实现起来也是相当简单,在Javascript程序中被大量采用,但是执行print( ,"ten");这样的操作会发生错误,在某些情况下如果0/null/undefined为有效值时,执行y = y || 'default'会不分青红皂白的把这些值过滤掉。
  • Using number of arguments(根据参数数量来实现重载)
  1. function print(num,str){
  2. var len=arguments.length;
  3. if (len===1) {
  4. console.log("Here is num "+num);
  5. }
  6. else if (len===2) {
  7. console.log("Here is num "+num);
  8. console.log("Here is string "+str);
  9. }
  10. else{
  11. console.log("看....,飞碟!");
  12. }
  13. }
  14. print();//看....,飞碟!
  15. print(10);//Here is num 10
  16. print(10,"ten");
  17. //Here is num 10
  18. //Here is string ten
从上面的结果看,如果出入的参数只有一个字符串,仍然无法打印。

  • Checking types of arguments (根据参数类型来实现重载)
  1. function print(num,str){
  2. if (typeof num=="number") {
  3. console.log("Here is num "+num);
  4. }
  5. if (typeof str=="string") {
  6. console.log("Here is string "+str);
  7. }
  8. }
  9. print();//无输出
  10. print(10);//Here is num 10
  11. print('',"ten");//Here is string ten
  12. print(10,"ten");
  13. // Here is num 10
  14. // Here is string ten
这种方法的结果算是比较满足函数重载的行为了,但是有没有发现如果只想打印字符串时,也不得不传入两个参数,并且要求开发人员知道函数内部能过滤掉第一个参数,同时检测参数数量和参数类型可以解决这个问题。
下面的例子中直接借用了arguments数组
 
  1. function print(num,str){
  2. var len=arguments.length;
  3. if((len===1)&& (typeof arguments[0]==="number")){
  4. console.log("Here is num "+arguments[0]);
  5. }
  6. else if ((len===1)&& (typeof arguments[0]==="string")) {
  7. console.log("Here is string "+arguments[0]);
  8. }
  9. else if((len===2)&& (typeof arguments[0]==="number")&& (typeof arguments[1]==="string")) {
  10. console.log("Here is num "+arguments[0]);
  11. console.log("Here is string "+arguments[1]);
  12. }
  13. else{
  14. console.log("看....,飞碟!");
  15. }
  16. }
  17. print();//看....,飞碟!
  18. print(10);//Here is num 10
  19. print("ten");//Here is string ten
  20. print(10,"ten");
  21. // Here is num 10
  22. // Here is string ten

在预期参数较多的情况下,上述的所有方法可能都不尽如人意,检测参数类型会拖慢执行速度,并且并且需要检测的类型过多, Arrays, nulls, Objects, etc.而且如果有几个参数的类型相同呢,那岂不是容易混淆,这时Javascript中有一种代码风格被广泛使用:使用对象作为函数的最后一个参数,对象可以承载任何类型的键值对。
  1. function foo(a, b, opts) {
  2. }
  3. foo(1, 2, {"method":"add"});
  4. foo(3, 4, {"test":"equals", "bar":"tree"});
在代码中只需要检测对象的某个键值是否存在来做相应处理。

至此,前面模拟函数重载的方法的基本思想都是基于传递的参数定义一个有很多不同功能的函数,通过使用if-then或者switch子句处理不同的行为,但是一旦事情开始变得的复杂,大量的分支语句就会导致代码笨拙,John Resig在《Secrets of the JavaScript Ninja》(中译名:JavaScript忍者秘籍)中提到一种基于函数的length属性的重载方法
ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:length 。其中,length 属性表示函数希望接收的命名参数的个数,如下面的例子所示(见《Js高程P116页》)。
  1. function sayName(name){
  2. alert(name);
  3. }
  4. function sum(num1, num2){
  5. return num1 + num2;
  6. }
  7. function sayHi(){
  8. alert("hi");
  9. }
  10. alert(sayName.length); //1
  11. alert(sum.length); //2
  12. alert(sayHi.length); //0
因此,对于一个函数,在参数方面,我们可以确定两件事
■ 通过其length属性,可以知道声明了多少命名参数
■ 通过arguments.length,可以知道在调用时传入了多少参数
下面介绍一下大牛是如何实现借助这个特性实现函数的重载的
  1. var ninja = {};
  2. addMethod(ninja,'whatever',function(){ /* do something */ });
  3. addMethod(ninja,'whatever',function(a){ /* do something else */ });
  4. addMethod(ninja,'whatever',function(a,b){ /* yet something else */ });
在这里,先创建一个对象,然后使用同样的名称(whatever)将方法添加到该对象上,只不过每个重载的函数都是单独的,注意每个重载的参数个数都不相同。通过这种方式,真正为每个重载都创建了一个独立的匿名函数。漂亮且整洁!现在的关键就是实现addMethod()函数,大牛的实现相当巧妙:
  1. function addMethod(object, name, fn) {
  2.   var old = object[name]; //保存原有的函数,因为调用的时候可能不匹配传入的参数个数
  3.   object[name] = function() { // 重写了object[name]的方法,是一个匿名函数
  4.     // 如果该匿名函数的形参个数和实参个数匹配,就调用该函数
  5.     if(fn.length === arguments.length) {
  6.       return fn.apply(this, arguments);
  7.     // 如果传入的参数不匹配,就调用原有的参数
  8.     } else if(typeof old === "function") {
  9.       return old.apply(this, arguments);
  10.     }
  11.   }
  12. }
 现在,我们一起来分析一个这个addMethod函数,它接收3个参数:
  • 第一个为要绑定方法的对象,
  • 第二个为绑定方法所用的属性名称,
  • 第三个为需要绑定的方法(一个匿名函数)
 好吧,一言不合就举个例子吧

  1. //addMethod
  2. function addMethod(object, name, fn) {
  3.   var old = object[name];
  4.   object[name] = function() {
  5.     if(fn.length === arguments.length) {
  6.       return fn.apply(this, arguments);
  7.     } else if(typeof old === "function") {
  8.       return old.apply(this, arguments);
  9.     }
  10.   }
  11. }
  12. var printer= {};
  13. // 不传参数时,打印“飞碟”
  14. addMethod(printer, "print", function() {
  15.   console.log("看,飞碟!");
  16. });
  17. // 传一个参数时,打印数字
  18. addMethod(printer, "print", function(num) {
  19. console.log("Here is num "+num);
  20. });
  21. // 传两个参数时,打印数字和字符串
  22. addMethod(printer, "print", function(num,str) {
  23.  console.log("Here is num "+ num);
  24. console.log("Here is string "+str);
  25. });
  26. // 测试:
  27. printer.print(); //看,飞碟!
  28. printer.print(10); //Here is num 10
  29. printer.print(10, "ten");
  30. //Here is num 10
  31. //Here is string ten
对上面的思想稍微改造一下,我们可以直接把重载功能放在函数对象原型上。
  1. // Function.prototype.overload - By 沐浴星光
  2. Function.prototype.overload = function(fn) {
  3. var old= this;
  4. return function() {
  5. if (fn.length===arguments.length) {
  6. return fn.apply(this,arguments);
  7. }else{
  8. return old.apply(this,arguments);
  9. }
  10. }
  11. }
  12. // 不传参数时,打印“飞碟”
  13. function printNone() {
  14.   console.log("看,飞碟!");
  15. }
  16. // 传一个参数时,打印数字
  17. function printInt(num) {
  18. console.log("Here is num "+num);
  19. }
  20. // 传两个参数时,打印数字和字符串
  21. function printBoth(num,str) {
  22. console.log("Here is num "+ num);
  23. console.log("Here is string "+str);
  24. }
  25. print=function(){};
  26. print=print.overload(printNone).overload(printInt).overload(printBoth);
  27. print();//看,飞碟!
  28. print(10);//Here is num 10
  29. print(10,"ten");
  30. // Here is num 10
  31. // Here is string ten
 
 

参考:
《Javascript高级程序设计-第三版》
《JavaScript忍者秘籍》
扩展阅读:
Function Overloading in JavaScript | Chris West's Blog




Javascript函数重载,存在呢—还是存在呢?的更多相关文章

  1. javascript 函数重载 overloading

    函数重载 https://en.wikipedia.org/wiki/Function_overloading In some programming languages, function over ...

  2. JavaScript函数重载

    译者按: jQuery之父John Resig巧妙地利用了闭包,实现了JavaScript函数重载. 原文: JavaScript Method Overloading 译者: Fundebug 为了 ...

  3. [转载]浅谈JavaScript函数重载

     原文地址:浅谈JavaScript函数重载 作者:ChessZhang 上个星期四下午,接到了网易的视频面试(前端实习生第二轮技术面试).面了一个多小时,自我感觉面试得很糟糕的,因为问到的很多问题都 ...

  4. javascript arguments与javascript函数重载

    1.所 有的函数都有属于自己的一个arguments对象,它包括了函所要调用的参数.他不是一个数组,如果用typeof arguments,返回的是’object’.虽然我们可以用调用数据的方法来调用 ...

  5. JavaScript “函数重载”

    函数重载(function overloading)必须依赖两件事情:判断传入参数数量的能力和判断传入参数类型的能力. JavaScript的每个函数都带有一个仅在这个函数范围内作用的变量argume ...

  6. 浅谈JavaScript函数重载

    上个星期四下午,接到了网易的视频面试(前端实习生第二轮技术面试).面了一个多小时,自我感觉面试得很糟糕的,因为问到的很多问题都很难,根本回答不上来.不过那天晚上,还是很惊喜的接到了HR面电话.现在HR ...

  7. javascript 函数重载另一种实现办法

    最近在读javascript忍者 感受下jquery作者 john Resig对于js的独到见解. 先上代码: function addMethod(object,name,fn){ var old ...

  8. 理解javascript函数的重载

        javascript其实是不支持重载的,这篇文章会和java语言函数的重载对比来加深对javascript函数重载的理解.       以下我会假设读者不了解什么是重载所以会有一些很基础的概念 ...

  9. JavaScript模拟函数重载

    JavaScript是没有函数重载的,但是我们可以通过其他方法来模拟函数重载,如下所示: <!DOCTYPE html> <html> <head> <met ...

随机推荐

  1. less简介

    Less是一种动态的样式语言.Less扩展了CSS的动态行为,比如说,设置变量(Variables).混合书写模式(mixins).操作(operations)和功能(functions)等等,最棒的 ...

  2. Thinking in java学习笔记之final

  3. eclipse注释快捷键(含方法注释)

    整段注释: /*public boolean executeUpdate(String sql) { System.out.println(sql); boolean mark=false; try ...

  4. CLI:如何使用Go开发命令行

    CLI或者"command line interface"是用户在命令行下交互的程序.由于通过将程序编译到一个静态文件中来减少依赖,一次Go特别适合开发CLI程序.如果你编写过安装 ...

  5. mysql-5.7日志设置

    环境 Windows10企业版X64 mysql安装目录:D:\mysql-5.7.15-winx64. 在mysql安装目录下手工新建一个log目录:mysql\log. mysql\my.ini内 ...

  6. [NHibernate]基本配置与测试

    目录 写在前面 nhibernate文档 搭建项目 映射文件 持久化类 辅助类 数据库设计与连接配置 测试 总结 写在前面 一年前刚来这家公司,发现项目中使用的ORM是Nhibernate,这个之前确 ...

  7. LYDSY模拟赛day1 String Master

    /* 暴力枚举两个后缀,计算最长能匹配多少前缀. 最优策略一定是贪心改掉前 k 个失配的字符. 时间复杂度 O(n3). */ #include<cstdio> ],b[]; int ma ...

  8. PHP计算一年有多少周,每周开始日期和结束日期

    一年有多个周,每周的开始日期和结束日期 参考代码一:[正在使用的版本] <?php header("Content-type:text/html;charset=utf-8" ...

  9. 搭建一个简单struts2框架的登陆

    第一步:下载struts2对应的jar包,可以到struts官网下载:http://struts.apache.org/download.cgi#struts252 出于学习的目的,可以把整个完整的压 ...

  10. Visual Studio常用快捷键

    1. 代码自动对齐:CTRL+K+F 2. 撤销---使用组合键“Ctrl+Z”进行撤销操作 3. 反撤销---使用组合键“Ctrl+Y”进行反撤销操作 4. 使用组合键“Ctrl+J”或者使用组合键 ...