admin管理员组文章数量:1794759
JS方法/函数重载的姿势
JavaScript不支持重载的语法,它没有重载所需要的函数签名。
ECMAScript函数不能像传统意义上那样实现重载。而在其他语言(如 Java)中,可以为一个函数编写两个定义,只要这两个定义的签名(接受的参数的类型和数量)不同即可。如前所述,ECMAScirpt函数没有签名,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的。 — JavaScript高级程序设计(第3版)3.7.2小节
而且在JavaScript中,函数名本身就是变量,函数声明类似于变量赋值。当同个函数名被多次声明时,后声明的内容将覆盖前面的内容,如下所示:
function addSomeNumber(num){ return num + 100; } function addSomeNumber(num){ return num + 200; } var result = addSomeNumber(100); // => 300尽管JavaScript无法做到真正的重载,但是可以通过检查传入函数中参数的类型和数量并作相应的处理,从而实现重载的效果,曲线救国。 下面介绍几种JS重载的姿势:
姿势一:借助流程控制语句通过判断传入参数的数量(arguments.length),执行相应的代码块。
function doSomething(){ switch(arguments.length){ case 0: /* 代码块 */ break; ... case n: /* 代码块 */ break; } } 姿势二:巧用闭包特性jQuery创始人John Resig在《Secrets of the JavaScript Ninja》的第4.4.2小节中介绍了一种方法,如下所示,可通过addMethod函数将对象ninja的whatever方法进行重载。
var ninja = {}; addMethod(ninja, 'whatever', function(){/* code */}); addMethod(ninja, 'whatever', function(a){/* code */}); addMethod(ninja, 'whatever', function(a,b){/* code */});addMethod函数接收3个参数:目标对象、目标方法名、函数体,当函数被调用时: 1. 先将目标object[name]的值存入变量old中,因此起初old中的值可能不是一个函数; 2. 接着向object[name]赋值一个代理函数,并且由于变量old、fn在代理函数中被引用,所以old、fn将常驻内存不被回收。
function addMethod(object, name, fn) { var old = object[name]; // 保存前一个值,以便后续调用 object[name] = function(){ // 向object[name]赋值一个代理函数 // 判断fn期望接收的参数与传入参数个数是否一致 if (fn.length == arguments.length) // 若是,则调用fn return fn.apply(this, arguments) else if (typeof old == 'function') // 若否,则判断old的值是否为函数 // 若是,则调用old return old.apply(this, arguments); }; }代理函数被调用时: 1. 先判断传入参数与其父级作用域中fn期望接收参数的个数是否一致,若是则调用该fn; 2. 若否,则判断其父级作用域中old值类型是否为函数,若是则调用该old; 3. 当old中存有上一次生成的代理函数时,则会重复前面两个步骤,直至old值不为代理函数。
上述两种方法都是通过检查参数个数来实现重载,不区分参数类型。 此外,方法1在继承时重载的那些函数无法被重写,而方法2通过逐个执行代理函数,比对参数个数,直至找到目标函数,效率不高。 为了解决这些问题,我构思设计一个方法,见下文。若你知道更好的方法或思路,还请留言指点一下。
姿势三:巧用引用类型特性
核心思想:由于ECMAScript函数是一种引用类型对象,可扩展属性与方法。借此通过创建一个容器用于存储要重载的函数,并将容器挂载到代理函数上以便后续访问,而代理函数利用闭包特性访问容器。 重载顺序:首先查找参数类型匹配的函数,其次查找参数个数匹配的函数。 存储格式:键值对,键名由逗号与参数个数或参数类型组成,键值为要重载的函数,如下:
{ ',0': function(){/* code */}, ',1': function(a){/* code */}, ',string,number': function(a,b){/* code */} }工具函数被调用时: 1. 先判断是否已重载过,若有,直接将要重载的函数按格式存入容器; 2. 若未重载过,则创建一个容器变量; 3. 判断未重载前的值是否为一个函数,若是,则以逗号+参数个数的格式存入容器; 4. 将要重载的函数存入容器; 5. 代理原函数,并将容器挂载到代理函数上; 6. 当代理函数被调用时,将依次查找容器中匹配的函数并调用。
/** * 重载工具函数 * @param {Object} ctx - 上下文 * @param {String} name - 函数名 * @param {Function} fn - 函数体 * @param {String} type - 参数类型 * @author 范围兄 <ambit_tsai@qq> * @example 不指定参数类型 * overload(obj, 'do', function(){...}); * overload(obj, 'do', function(a){...}); * @example 指定参数类型 * overload(obj, 'do', function(a,b){...}, 'string,number'); */ function overload(ctx, name, fn, type){ type = type? type.trim().toLowerCase(): fn.length; // 已重载过 if(typeof ctx[name]==='function' && typeof ctx[name]._$fnMap==='object'){ ctx[name]._$fnMap[','+type] = fn; // 将fn存入_$fnMap return; } // 未重载过 var fnMap = {}; // 容器 if(typeof ctx[name] === 'function'){ // 若ctx[name]是一个函数,则存入容器 fnMap[','+ctx[name].length] = ctx[name]; } fnMap[','+type] = fn; ctx[name] = function overloading(){ // 代理 var args = arguments, len = args.length, type, i; for(i=0, type=''; i<len; ++i){ // 计算参数类型 type += ',' + typeof args[i]; } // 依次匹配:参数类型->参数个数 if(fnMap[type]) return fnMap[type].apply(this, args); if(fnMap[','+len]) return fnMap[','+len].apply(this, args); throw 'Overload: no matched function'; }; ctx[name]._$fnMap = fnMap; // 将fnMap挂载到代理上 }都看到这了,给点个赞呗 (。◕∀◕。)
版权声明:本文标题:JS方法函数重载的姿势 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1686490611a73406.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论