Javascript 对象方式实现命名参数调用(下):自动包装偏函数

上篇,咱们实现了一个简单的函数。其可以采用灵活的多种调用方式:如下:

help({name:'john', sex:'M'});  
help("name:'john', sex:'M'");  
help('john',25);  
help(['john',25]);  

但同时带来的问题是,实现这样的一个函数,其真正的功能性代码只有一行,而为了实现上述的调用方式的支持代码却有12行。要想在自己的项目中采用这种技巧,让所有的自定义函数都拥有这样的能力肯定是一件可怕的体力活。

我们希望有这样一个东西:通过非常少的代码(最好一行),就可以把我们要调用的函数包装成一个新函数,而此新函数能支持上述的多重调用方式,最后转换为一种调用方式(命名参数调用方式)来调用我们的函数。像这样:

function helpRaw(params){ //命名参数版本  
   console.log(params.name,params.sex, param.age) ;  
}  
var help=makeSuperFunction(helpRaw, {name: "untitled" , age:18 , sex:'M' } /*默认值*/);  
help({name:'john', sex:'M'});  
help("name:'john', sex:'M'");  
help('john',25);  
help(['john',25]);  

我们就叫它 makeSuperFunction。有了它,我们甚至都不用考虑默认缺省值。可以假设传进来的参数必然完整,完全不用实现默认值逻辑。有点意思?让我们一步一步来实现它吧。

你调用或者不调用我,我就在这里。

makeSuperFunction 是一个标准的偏函数(返回函数的函数)。其返回值是一个函数,第一个参数是最终被调用的我们写的原始函数,第二个参数是此函数调用的完整default 参数对象。从功能上,很显然,makeSuperFunction 返回的函数就是根据传入的参数类型来决定调用不同方式的函数。看上去就像这样:

   var adapterFuncs = {object: makeFuncViaObject(func,defaultOption),
                        string: makeFuncViaString(func,defaultOption),
                        number: makeFuncViaParams(func,defaultOption) };
    //根据参数类型,定义不同的函数调用方式
    return function (option) {
        return adapterFuncs[typeof option](arguments);
    }

这个浅显易懂,根据传入的参数类型,返回不同的函数。这些函数又分别对应不同的调用方式。如果,大家熟悉C++,会有一种似曾相识的感觉。是不是非常像C++中的晚绑定,但是更加灵活,而且几乎没有任何限制。
所以下面,我们分别实现三个偏函数(没错,返回的函数还是一个偏函数),来分别处理三种不同的调用方式。他们是:
对象方式(包括数组方式,因为数组也是对象),字符串方式,原生的参数列表方式:

  1. 对象方式(包括数组方式):
   function makeFuncViaObject(func,defaultOption){
        return function (args){
            var option=args[0];
            if (Array.isArray(option)){//判断是否数组,如果是转换为对象
                option= _.object(_.keys(defaultOption),option);
            }
            option=_(option).defaults(defaultOption);//加入默认值
            return func(option);//调用用户函数,实现业务逻辑
        }
    }
  1. 字符串方式:
    function makeFuncViaString(func){  //support string model call
        return function(args){
            var option=args[0];
            eval("var options=[{" + option + "}]");
            return func(options);
        }
    }

然后是

  1. 参数列表方式:
    function makeFuncViaParams(func,paramNameArray){//support normal mode call
        return function (args){
            var option= _.object(paramNameArray,args);
            return func([option]);
        }
    }

好吧,我们发现。由于最终提供的函数是对象方式的调用,又没有处理默认缺省值。所以后面两种方式(字符串和参数列表方式都需要处理对象方式中的默认缺省值的代码逻辑)。既然如此,我们直接调用好了。我整理了一下上面的代码,完整的代码应该是这样的:

完整的代码

function makeSuperFunction(func,defaultOption){
    function makeFuncViaString(func){  //support string model call
        return function(args){
            var option=args[0];
            var regexp=/\\w\\s*=\\s*\\w/;
            eval("var options=[{" + option + "}]");
            return func(options);
        }
    }
    function makeFuncViaParams(func,paramNameArray){//support normal mode call
        return function (args){
            var option= _.object(paramNameArray,args);
            return func([option]);
        }
    }
    function makeFuncViaObject(func,defaultOption){//object mode support default value ; support Array
        return function (args){
            var option=args[0];
            if (Array.isArray(option)){
                option= _.object(_.keys(defaultOption),option);
            }
            option=_(option).defaults(defaultOption);
            return func(option);
        }
    }
    var adapterFuncs = {object: makeFuncViaObject(func,defaultOption),
                        string: makeFuncViaString(makeFuncViaObject(func,defaultOption)),
                        number: makeFuncViaParams(makeFuncViaObject(func,defaultOption), _.keys(defaultOption)) };
    return function (option) {
        if (option == undefined) {
            arguments[0] = option= {};
        }
        return adapterFuncs[typeof option](arguments);
    }
}

我在函数映射对象中,直接把后两种偏函数的调用目标函数包装为对象调用版本。这样,另外两个函数就不需要知道这些逻辑,直接调用即可。所有的默认缺省值支持,都交由对象方式版本来完成。
至此,我们基本已将达到了上篇所说的要求。有了上述这段代码,再加上:

function helpRaw(params){ //命名参数版本  
   console.log(params.name,params.sex, param.age) ;  
}  
var help=makeSuperFunction(helpRaw, {name: "untitled" , age:18 , sex:'M' } /*默认值*/);  
help({name:'john', sex:'M'});  
help("name:'john', sex:'M'");  
help('john',25);  
help(['john',25]); 

现在,是见证奇迹的时刻——刘谦

现在,大家终于可以完整运行一下了。然后,换一个别的函数再试试。嘿嘿。
当然,上述代码还有需要完善的地方。比如:
如果我们采用参数列表方式来调用,但第一个参数就是字符串怎么办?
这也许可以通过头尾分别加上“{”,“}”变成JSON 格式的字符串然后判断其有效性来实现。但其效率不高,而且如果这第一个参数就是合法的JSON但不包括“{}”又怎么办?
其实,在实际的工作中,字符串方式使用的非常少,其基本没有带来任何好处。而且所有可以使用字符串方式的情况都可以采用 JSON 转换为对象方式来调用。
同样道理,参数列表的第一个参数就是对象的情况也无法在上述的实现中很好的解决。至于怎么解决,这里我就不再赘述了,留待大家来研究改进吧。

你可能感兴趣的:(Javascript 对象方式实现命名参数调用(下):自动包装偏函数)