上篇,咱們實現(xiàn)了一個簡單的函數(shù)。其可以采用靈活的多種調(diào)用方式:如下:
help({name:'john', sex:'M'});
help("name:'john', sex:'M'");
help('john',25);
help(['john',25]);
但同時帶來的問題是,實現(xiàn)這樣的一個函數(shù),其真正的功能性代碼只有一行,而為了實現(xiàn)上述的調(diào)用方式的支持代碼卻有12行。要想在自己的項目中采用這種技巧,讓所有的自定義函數(shù)都擁有這樣的能力肯定是一件可怕的體力活。
我們希望有這樣一個東西:通過非常少的代碼(最好一行),就可以把我們要調(diào)用的函數(shù)包裝成一個新函數(shù),而此新函數(shù)能支持上述的多重調(diào)用方式,最后轉(zhuǎn)換為一種調(diào)用方式(命名參數(shù)調(diào)用方式)來調(diào)用我們的函數(shù)。像這樣:
function helpRaw(params){ //命名參數(shù)版本
console.log(params.name,params.sex, param.age) ;
}
var help=makeSuperFunction(helpRaw, {name: "untitled" , age:18 , sex:'M' } /*默認(rèn)值*/);
help({name:'john', sex:'M'});
help("name:'john', sex:'M'");
help('john',25);
help(['john',25]);
我們就叫它 makeSuperFunction。有了它,我們甚至都不用考慮默認(rèn)缺省值??梢约僭O(shè)傳進來的參數(shù)必然完整,完全不用實現(xiàn)默認(rèn)值邏輯。有點意思?讓我們一步一步來實現(xiàn)它吧。
你調(diào)用或者不調(diào)用我,我就在這里。
makeSuperFunction 是一個標(biāo)準(zhǔn)的偏函數(shù)(返回函數(shù)的函數(shù))。其返回值是一個函數(shù),第一個參數(shù)是最終被調(diào)用的我們寫的原始函數(shù),第二個參數(shù)是此函數(shù)調(diào)用的完整default 參數(shù)對象。從功能上,很顯然,makeSuperFunction 返回的函數(shù)就是根據(jù)傳入的參數(shù)類型來決定調(diào)用不同方式的函數(shù)。看上去就像這樣:
var adapterFuncs = {object: makeFuncViaObject(func,defaultOption),
string: makeFuncViaString(func,defaultOption),
number: makeFuncViaParams(func,defaultOption) };
//根據(jù)參數(shù)類型,定義不同的函數(shù)調(diào)用方式
return function (option) {
return adapterFuncs[typeof option](arguments);
}
這個淺顯易懂,根據(jù)傳入的參數(shù)類型,返回不同的函數(shù)。這些函數(shù)又分別對應(yīng)不同的調(diào)用方式。如果,大家熟悉C++,會有一種似曾相識的感覺。是不是非常像C++中的晚綁定,但是更加靈活,而且?guī)缀鯖]有任何限制。
所以下面,我們分別實現(xiàn)三個偏函數(shù)(沒錯,返回的函數(shù)還是一個偏函數(shù)),來分別處理三種不同的調(diào)用方式。他們是:
對象方式(包括數(shù)組方式,因為數(shù)組也是對象),字符串方式,原生的參數(shù)列表方式:
- 對象方式(包括數(shù)組方式):
function makeFuncViaObject(func,defaultOption){
return function (args){
var option=args[0];
if (Array.isArray(option)){//判斷是否數(shù)組,如果是轉(zhuǎn)換為對象
option= _.object(_.keys(defaultOption),option);
}
option=_(option).defaults(defaultOption);//加入默認(rèn)值
return func(option);//調(diào)用用戶函數(shù),實現(xiàn)業(yè)務(wù)邏輯
}
}
- 字符串方式:
function makeFuncViaString(func){ //support string model call
return function(args){
var option=args[0];
eval("var options=[{" + option + "}]");
return func(options);
}
}
然后是
- 參數(shù)列表方式:
function makeFuncViaParams(func,paramNameArray){//support normal mode call
return function (args){
var option= _.object(paramNameArray,args);
return func([option]);
}
}
好吧,我們發(fā)現(xiàn)。由于最終提供的函數(shù)是對象方式的調(diào)用,又沒有處理默認(rèn)缺省值。所以后面兩種方式(字符串和參數(shù)列表方式都需要處理對象方式中的默認(rèn)缺省值的代碼邏輯)。既然如此,我們直接調(diào)用好了。我整理了一下上面的代碼,完整的代碼應(yīng)該是這樣的:
完整的代碼
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);
}
}
我在函數(shù)映射對象中,直接把后兩種偏函數(shù)的調(diào)用目標(biāo)函數(shù)包裝為對象調(diào)用版本。這樣,另外兩個函數(shù)就不需要知道這些邏輯,直接調(diào)用即可。所有的默認(rèn)缺省值支持,都交由對象方式版本來完成。
至此,我們基本已將達到了上篇所說的要求。有了上述這段代碼,再加上:
function helpRaw(params){ //命名參數(shù)版本
console.log(params.name,params.sex, param.age) ;
}
var help=makeSuperFunction(helpRaw, {name: "untitled" , age:18 , sex:'M' } /*默認(rèn)值*/);
help({name:'john', sex:'M'});
help("name:'john', sex:'M'");
help('john',25);
help(['john',25]);
現(xiàn)在,是見證奇跡的時刻——劉謙
現(xiàn)在,大家終于可以完整運行一下了。然后,換一個別的函數(shù)再試試。嘿嘿。
當(dāng)然,上述代碼還有需要完善的地方。比如:
如果我們采用參數(shù)列表方式來調(diào)用,但第一個參數(shù)就是字符串怎么辦?
這也許可以通過頭尾分別加上“{”,“}”變成JSON 格式的字符串然后判斷其有效性來實現(xiàn)。但其效率不高,而且如果這第一個參數(shù)就是合法的JSON但不包括“{}”又怎么辦?
其實,在實際的工作中,字符串方式使用的非常少,其基本沒有帶來任何好處。而且所有可以使用字符串方式的情況都可以采用 JSON 轉(zhuǎn)換為對象方式來調(diào)用。
同樣道理,參數(shù)列表的第一個參數(shù)就是對象的情況也無法在上述的實現(xiàn)中很好的解決。至于怎么解決,這里我就不再贅述了,留待大家來研究改進吧。