JS组合函数实例分析
导读:本文共4939.5字符,通常情况下阅读需要16分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要: 组合函数含义函数编程就像拼乐高!乐高有各式各样的零部件,我们将它们组装拼接,拼成一个更大的组件或模型。函数编程也有各种功能的函数,我们将它们组装拼接,用于实现某个特定的功能。下面来看一个例子,比如我们要使用这两个函数来分析文本字符串:functionwords(str){returnString(str).toLowerCase().split(/\s|\... ...
目录
(为您整理了一些要点),点击可以直达。组合函数
含义
函数编程就像拼乐高!
乐高有各式各样的零部件,我们将它们组装拼接,拼成一个更大的组件或模型。
函数编程也有各种功能的函数,我们将它们组装拼接,用于实现某个特定的功能。
下面来看一个例子,比如我们要使用这两个函数来分析文本字符串:
functionwords(str){returnString(str).toLowerCase().split(/\s|\b/).filter(functionalpha(v){return/^[\w]+$/.test(v);});}functionunique(list){varuniqList=[];for(leti=0;i<list.length;i++){if(uniqList.indexOf(list[i])===-1){uniqList.push(list[i]);}}returnuniqList;}vartext="Tocomposetwofunctionstogether";varwordsFound=words(text);varwordsUsed=unique(wordsFound);wordsUsed;// ["to","compose","two","functions","together"]
不用细看,只用知道:我们先用 words 函数处理了 text,然后用 unique 函数处理了上一处理的结果 wordsFound;
这样的过程就好比生产线上加工商品,流水线加工。
想象一下,如果你是工厂老板,还会怎样优化流程、节约成本?
这里作者给了一种解决方式:去掉传送带!
即减少中间变量,我们可以这样调用:
varwordsUsed=unique(words(text));wordsUsed
确实,少了中间变量,更加清晰,还能再优化吗?
我们还可以进一步把整个处理流程封装到一个函数内:
functionuniqueWords(str){returnunique(words(str));}uniqueWords(text)
这样就像是一个黑盒,无需管里面的流程,只用知道这个盒子输入是什么!输出是什么!输入输出清晰,功能清晰,非常“干净”!如图:
与此同时,它还能被搬来搬去,或再继续组装。
我们回到 uniqueWords() 函数的内部,它的数据流也是清晰的:
uniqueWords<--unique<--words<--text
封装盒子
上面的封装 uniqueWords 盒子很 nice ,如果要不断的封装像 uniqueWords 的盒子,我们要一个一个的去写吗?
functionuniqueWords(str){returnunique(words(str));}functionuniqueWords_A(str){returnunique_A(words_A(str));}functionuniqueWords_B(str){returnunique_B(words_B(str));}...
所以,一切为了偷懒,我们可以写一个功能更加强大的函数来实现自动封装盒子:
functioncompose2(fn2,fn1){returnfunctioncomposed(origValue){returnfn2(fn1(origValue));};}//ES6箭头函数形式写法varcompose2=(fn2,fn1)=>origValue=>fn2(fn1(origValue));
接着,调用就变成了这样:
varuniqueWords=compose2(unique,words);varuniqueWords_A=compose2(unique_A,words_A);varuniqueWords_B=compose2(unique_B,words_B);
太清晰了!
任意组合
上面,我们组合了两个函数,实际上我们也可以组合 N 个函数;
finalValue<--func1<--func2<--...<--funcN<--origValue
比如用一个 compose 函数来实现(敲重点):
functioncompose(...fns){returnfunctioncomposed(result){//拷贝一份保存函数的数组varlist=fns.slice();while(list.length>0){//将最后一个函数从列表尾部拿出//并执行它result=list.pop()(result);}returnresult;};}//ES6箭头函数形式写法varcompose=(...fns)=>result=>{varlist=fns.slice();while(list.length>0){//将最后一个函数从列表尾部拿出//并执行它result=list.pop()(result);}returnresult;};
基于前面 uniqueWords(..) 的例子,我们进一步再增加一个函数来处理(过滤掉长度小于等于4的字符串):
functionskipShortWords(list){varfilteredList=[];for(leti=0;i<list.length;i++){if(list[i].length>4){filteredList.push(list[i]);}}returnfilteredList;}vartext="Tocomposetwofunctionstogether";varbiggerWords=compose(skipShortWords,unique,words);varwordsUsed=biggerWords(text);wordsUsed;//["compose","functions","together"]
这样 compose 函数就有三个入参且都是函数了。我们还可以利用偏函数的特性实现更多:
functionskipLongWords(list){/*..*/}varfilterWords=partialRight(compose,unique,words);//固定unique函数和words函数varbiggerWords=filterWords(skipShortWords);varshorterWords=filterWords(skipLongWords);biggerWords(text);shorterWords(text);
filterWords 函数是一个更具有特定功能的变体(根据第一个函数的功能来过滤字符串)。
compose 变体
compose(..)函数非常重要,但我们可能不会在生产中使用自己写的 compose(..),而更倾向于使用某个库所提供的方案。了解其底层工作的原理,对我们强化理解函数式编程也非常有用。
我们理解下 compose(..) 的另一种变体 —— 递归的方式实现:
functioncompose(...fns){//拿出最后两个参数var[fn1,fn2,...rest]=fns.reverse();varcomposedFn=functioncomposed(...args){returnfn2(fn1(...args));};if(rest.length==0)returncomposedFn;returncompose(...rest.reverse(),composedFn);}//ES6箭头函数形式写法varcompose=(...fns)=>{//拿出最后两个参数var[fn1,fn2,...rest]=fns.reverse();varcomposedFn=(...args)=>fn2(fn1(...args));if(rest.length==0)returncomposedFn;returncompose(...rest.reverse(),composedFn);};
通过递归进行重复的动作比在循环中跟踪运行结果更易懂,这可能需要更多时间去体会;
基于之前的例子,如果我们想让参数反转:
varbiggerWords=compose(skipShortWords,unique,words);//变成varbiggerWords=pipe(words,unique,skipShortWords);
只需要更改 compose(..) 内部实现这一句就行:
...while(list.length>0){//从列表中取第一个函数并执行result=list.shift()(result);}...
虽然只是颠倒参数顺序,这二者没有本质上的区别。
抽象能力
你是否会疑问:什么情况下可以封装成上述的“盒子”呢?
这就很考验 —— 抽象的能力了!
实际上,有两个或多个任务存在公共部分,我们就可以进行封装了。
比如:
functionsaveComment(txt){if(txt!=""){comments[comments.length]=txt;}}functiontrackEvent(evt){if(evt.name!==undefined){events[evt.name]=evt;}}
就可以抽象封装为:
functionstoreData(store,location,value){store[location]=value;}functionsaveComment(txt){if(txt!=""){storeData(comments,comments.length,txt);}}functiontrackEvent(evt){if(evt.name!==undefined){storeData(events,evt.name,evt);}}
在做这类抽象时,有一个原则是,通常被称作 DRY(don't repeat yourself),即便我们要花时间做这些非必要的工作。
抽象能让你的代码走得更远! 比如上例,还能进一步升级:
functionconditionallyStoreData(store,location,value,checkFn){if(checkFn(value,store,location)){store[location]=value;}}functionnotEmpty(val){returnval!="";}functionisUndefined(val){returnval===undefined;}functionisPropUndefined(val,obj,prop){returnisUndefined(obj[prop]);}functionsaveComment(txt){conditionallyStoreData(comments,comments.length,txt,notEmpty);}functiontrackEvent(evt){conditionallyStoreData(events,evt.name,evt,isPropUndefined);}
这样 if 语句也被抽象封装了。
抽象是一个过程,程序员将一个名字与潜在的复杂程序片段关联起来,这样该名字就能够被认为代表函数的目的,而不是代表函数如何实现的。通过隐藏无关的细节,抽象降低了概念复杂度,让程序员在任意时间都可以集中注意力在程序内容中的可维护子集上。—— 《程序设计语言》
我们在本系列初始提到:“一切为了创造更可读、更易理解的代码。”
从另一个角度,抽象就是将命令式代码变成声命式代码的过程。从“怎么做”转化成“是什么”。
命令式代码主要关心的是描述怎么做来准确完成一项任务。声明式代码则是描述输出应该是什么,并将具体实现交给其它部分。
比如 ES6 增加的结构语法:
functiongetData(){return[1,2,3,4,5];}//命令式vartmp=getData();vara=tmp[0];varb=tmp[3];//声明式var[a,,,b]=getData();
</div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
JS组合函数实例分析的详细内容,希望对您有所帮助,信息来源于网络。