現(xiàn)在大公司的編程方式有:
1.oop(面向?qū)ο缶幊?;
2.aop(面向切面編程);
3.函數(shù)式編程(JavaScript Functional Programming);
范疇論Category Theory
- 函數(shù)式編程是范疇論的數(shù)學(xué)分支是一門很復(fù)雜的數(shù)學(xué),認(rèn)為世界上所有概念體系都可以抽象出一個(gè)個(gè)范疇
- 彼此之間存在某種關(guān)系概念劲赠、事物矩肩、對(duì)象等等,都構(gòu)成范疇愧捕。任何事物只要找出他們之間的關(guān)系奢驯,就能定義
- 箭頭表示范疇成員之間的關(guān)系,正式的名稱叫做“態(tài)射”(morphism)次绘。范疇論認(rèn)為瘪阁,同一個(gè)范疇的所有成員,就是不同狀態(tài)的“變形”(transformation)邮偎。通過(guò)“態(tài)射”管跺,一個(gè)成員可以變形成另一個(gè)成員
函數(shù)式編程5大特點(diǎn)
- 函數(shù)是
第一等公民
- 只用
表達(dá)式
,不用語(yǔ)句
- 沒(méi)有
副作用
- 不修改狀態(tài)
- 引用透明(函數(shù)運(yùn)行只靠參數(shù))
專業(yè)術(shù)語(yǔ)
- 純函數(shù)
- 函數(shù)的柯里化
- 函數(shù)組合
- Point Free
- 聲明式命令式代碼
- 惰性求值
純函數(shù)
對(duì)于相同的輸入禾进,永遠(yuǎn)會(huì)得到相同的輸出豁跑,而且沒(méi)有任何可觀察的副作用,也不依賴外部環(huán)境的狀態(tài)泻云。
var xs=[1,2,3,4,5];
//Array.slice是純函數(shù)艇拍,因?yàn)樗鼪](méi)有副作用,對(duì)于固定的輸入宠纯,輸出總是固定的
xs.slice(0,3); //[1,2,3]
xs.slice(0,3); //[1,2,3]
xs.splice(0,3); //[1,2,3]
xs.splice(0,3); //[4,5]
import _ from 'lodash';
var sin=_.memorize(x=>Math.sin(x));
var a=sin(1); //第一次計(jì)算的時(shí)候會(huì)稍慢一點(diǎn)
var b=sin(1); //第二次有了緩存淑倾,速度極快
//純函數(shù)不僅可以有效降低系統(tǒng)的復(fù)雜度,還有很多很棒的特性征椒,比如可緩存性
//惰性函數(shù)
不純
var min=18;
var checkage=function(age){
return age>min; //依賴于外部的min娇哆,導(dǎo)致不純
}
函數(shù)的柯里化
傳遞給函數(shù)一部分參數(shù)來(lái)調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)。
用柯里化來(lái)改造上面的不純函數(shù)
var checkage=min=>(age=>age>min);
var checkage18=checkage(18);
checkage18(20);
Point Free
- 把一些對(duì)象自帶的方法轉(zhuǎn)化成純函數(shù)碍讨,不要命名轉(zhuǎn)瞬即逝的中間變量
- 這個(gè)函數(shù)中治力,我們使用了
str
作為我們的中間變量,但這個(gè)中間變量除了讓代碼變得長(zhǎng)了一點(diǎn)以外是毫無(wú)意義的
const f=str=>str.toUpperCase().split('')
應(yīng)用
var toUpperCase=word=>word.toUpperCase();
var split=x=>(str=>str.split(x));
var f=compose(split('').toUppercase);
f("abcd efgh");
這種風(fēng)格能夠幫助我們減少不必要的命名勃黍,讓代碼保持簡(jiǎn)潔和通用
聲明式與命令式代碼
命令式代碼的意思就是宵统,我們通過(guò)編寫(xiě)一條又一條指令去讓計(jì)算機(jī)執(zhí)行一些動(dòng)作,這其中一般都會(huì)涉及到很多繁雜的細(xì)節(jié)覆获。而聲明式就要優(yōu)雅很多了马澈,我們通過(guò)寫(xiě)表達(dá)式的方式來(lái)聲明我們想干什么,而不是通過(guò)一步一步的指示弄息。
//命令式
let CEOs=[];
for(var i=0;i<companies.length;i++){
CEOs.push(companies[i].CEO);
}
//聲明式
let CEOs=companies.map(c=>c.CEO);
優(yōu)缺點(diǎn)
函數(shù)式編程的一個(gè)明顯的好處就是這種聲明式的代碼痊班,對(duì)于無(wú)副作用的純函數(shù),我們完全可以不考慮函數(shù)內(nèi)部是如何實(shí)現(xiàn)的摹量,專注于編寫(xiě)業(yè)務(wù)代碼涤伐。優(yōu)化代碼時(shí),目光只需要集中在這些穩(wěn)定堅(jiān)固的函數(shù)內(nèi)部即可缨称。
相反凝果,不純的函數(shù)式的代碼會(huì)產(chǎn)生副作用或者依賴外部系統(tǒng)環(huán)境,使用他們的時(shí)候總是要考慮這些不干凈的副作用睦尽。在復(fù)雜的系統(tǒng)中器净,這對(duì)于程序員的心智來(lái)說(shuō)是極大的負(fù)擔(dān)。
惰性求值
function fn(){
if(IE){//IE時(shí)
fn=a;
}else{//chrome時(shí)
fn=b;
}
return fn;
}
第一次執(zhí)行時(shí)會(huì)走if当凡,然后fn重新賦值山害,第二次執(zhí)行fn時(shí),直接賦值不用判斷宁玫,提高執(zhí)行效率
高階函數(shù)
函數(shù)當(dāng)參數(shù)粗恢,把傳入的函數(shù)做一個(gè)封裝柑晒,然后返回這個(gè)封裝函數(shù)欧瘪,達(dá)到更高程度的抽象
//命令式
var add=function(a,b){
return a+b;
};
funtion math(func,array){
return func(array[0],array[1]);
}
math(add,[1,2]); //3
尾調(diào)用優(yōu)化
指函數(shù)內(nèi)部的最后一個(gè)動(dòng)作是函數(shù)調(diào)用。該調(diào)用的返回值匙赞,直接返回給函數(shù)佛掖。函數(shù)調(diào)用自身,稱為遞歸涌庭。如果尾調(diào)用自身芥被,就稱為尾遞歸。遞歸需要保存大量的調(diào)用記錄坐榆,很容易發(fā)生棧溢出錯(cuò)誤拴魄,如果使用尾遞歸優(yōu)化,將遞歸變?yōu)檠h(huán),那么只需要保存一個(gè)調(diào)用記錄匹中,這樣就不會(huì)發(fā)生棧溢出錯(cuò)誤了夏漱。
//不是尾遞歸,無(wú)法優(yōu)化
function factorial(n){
if(n===1) return 1;
return n*factorial(n-1);
}
//尾遞歸
function factorial(n,total){
if(n===1) return total;
return factorial(n-1,n*total);
}//ES6強(qiáng)制使用尾遞歸
普通遞歸時(shí)顶捷,內(nèi)存需要記錄調(diào)用的堆棧所出的深度和位置信息挂绰。在最低層計(jì)算返回值,再根據(jù)記錄的信息服赎,跳會(huì)上一層級(jí)計(jì)算葵蒂,然后再跳回到更高一層,依次運(yùn)行重虑,直到最外層的調(diào)用函數(shù)践付。在cpu計(jì)算和內(nèi)存會(huì)消耗很多,而且當(dāng)深度過(guò)大時(shí)嚎尤,會(huì)出現(xiàn)堆棧溢出
function sum(x){
if(x===1) return 1;
return x+sum(x-1);
}
sum(5); //15 遞歸
function sum(x,total){
if(x===1) return x+total;
return sum(x-1,x+total);
}
sum(5,0);
sum(4,5);
sum(3,9);
sum(2,12);
sum(1,14);
15 //尾遞歸荔仁,每次執(zhí)行之后,函數(shù)重新傳入?yún)?shù)芽死,直到結(jié)束
整個(gè)計(jì)算過(guò)程是線性的乏梁,調(diào)用一次sum(x,total)后,會(huì)進(jìn)入下一個(gè)棧关贵,相關(guān)的數(shù)據(jù)信息和跟隨進(jìn)入遇骑,不再放在堆棧上保存。當(dāng)計(jì)算完最后的值之后揖曾,直接返回到最上層的sum(5,0).這能有效的防止堆棧溢出落萎。
在ECMAScript6,我們將迎來(lái)尾遞歸優(yōu)化炭剪,通過(guò)尾遞歸優(yōu)化练链,javascript代碼在解釋成機(jī)器碼的時(shí)候,將會(huì)向while看起奴拦,也就是說(shuō)媒鼓,同時(shí)擁有數(shù)學(xué)表達(dá)能力和while的效能。
閉包
自己領(lǐng)會(huì)
函數(shù)式編程比較火熱的庫(kù)
- Rxjs //截流與仿抖
- cyclejs
- lodashjs
- underscorejs //開(kāi)始學(xué)最佳的庫(kù)
- ramadajs
需要學(xué)習(xí)
范疇與容器
- 我們可以把“范疇”想象成是一個(gè)容器错妖,里面包含兩樣?xùn)|西绿鸣。值(value)、值的變形關(guān)系暂氯,也就是函數(shù)潮模。
- 范疇論使用函數(shù),表達(dá)范疇之間的關(guān)系痴施。
- 伴隨著范疇論的發(fā)展擎厢,就發(fā)展出一整套函數(shù)的運(yùn)算方法究流。這套方法起初只用于數(shù)學(xué)運(yùn)算,后來(lái)有人將它在計(jì)算機(jī)上實(shí)現(xiàn)了动遭,就變成了今天的“函數(shù)式編程”梯嗽。
- 本質(zhì)上,函數(shù)式編程只是范疇論的運(yùn)算方法沽损,跟數(shù)理邏輯灯节、微積分、行列式式同一類東西绵估,都是數(shù)學(xué)方法炎疆,只是碰巧他能用來(lái)寫(xiě)程序。為什么函數(shù)式編程要求函數(shù)必須是純的国裳,不能有副作用形入?因?yàn)樗且环N數(shù)學(xué)運(yùn)算,原始目的就是求值缝左,不做其他事情亿遂,否則就無(wú)法滿足函數(shù)運(yùn)算法則了。
函子是函數(shù)式編程里面最重要的數(shù)據(jù)類型渺杉,也是基本的運(yùn)算單位和功能單位蛇数。它首先是一種范疇,也就是說(shuō)是越,是一個(gè)容器耳舅,包含了值和變形關(guān)系。比較特殊的是倚评,它的變形關(guān)系可以依次作用于每一個(gè)值浦徊,將當(dāng)前容器變形成另一個(gè)容器。
容器天梧、Functor(函子)
- $(...)返回的對(duì)象并不是一個(gè)原生的DOM對(duì)象盔性,而是對(duì)于原生對(duì)象的一種封裝,這在某種意義上就是一個(gè)"容器"(但它并不函數(shù)式)
- Functor(函子)遵守一些特定規(guī)則的容器類型
- Functor是一個(gè)對(duì)于函數(shù)調(diào)用的抽象呢岗,我們賦予容器自己去調(diào)用函數(shù)的能力冕香。把東西裝進(jìn)一個(gè)容器,只留出一個(gè)接口map給容器外的函數(shù)敷燎,map一個(gè)函數(shù)時(shí)暂筝,我們讓容器自己來(lái)運(yùn)行這個(gè)函數(shù)箩言,這樣容器就可以自由地選擇何時(shí)何地如何操作這個(gè)函數(shù)硬贯,以致于擁有惰性求值、錯(cuò)誤處理陨收、一步調(diào)用等非常牛掰的特性
var Container=function(x){
this.__value=x;
}
//函數(shù)式編程一般約定饭豹,函子有一個(gè)of方法
Container.of=x=>new Container(x);
//Container.of('abcd);
//一般約定鸵赖,函子的標(biāo)志就是容器具有map方法。該方法將容器里面的每一個(gè)值拄衰,映射到另一個(gè)容器它褪。
Container.prototype.map=function(f){
return Container.of(f(this.__value));
}
Container.of(3)
.map(x=>x+1) //Container(4)
.map(x=>'Result is '+x); //Container('Result is 4')
Maybe 函子
函子接受各種函數(shù),處理容器內(nèi)部的值翘悉,這里就有一個(gè)問(wèn)題茫打,容器內(nèi)部的值可能是一個(gè)空值(比如null),而外部函數(shù)未必有處理空值的機(jī)制妖混,如果傳入空值老赤,很可能就會(huì)出錯(cuò)。
Functor.of(null).map(function(s){
return s.toUpperCase();
});
//TypeError
class Maybe extends Functor{
map(f){
return this.val?Maybe.of(f(this.val)):Maybe.of(null);
}
}
Maybe.of(null).map(function(s){
return s.toUpperCase();
});
//Maybe(null) //報(bào)錯(cuò)制市,未定義
var Maybe=function(x){
this.__value=x;
}
Maybe.of=function(x){
return new Maybe(x);
}
Maybe.prototype.map=function(f){
return this.isNothing()?Maybe.of(null):Maybe.of(f(this.__value));
}
Maybe.prototype.isNothing=function(){
return (this.__value===null||this.__value===undefined);
}
Maybe(null) //不會(huì)報(bào)錯(cuò)了
//新的容器我們稱為Maybe
Either 函子
條件運(yùn)算if...else 是常見(jiàn)的運(yùn)算之一抬旺,函數(shù)式編程里面,使用Either函子表達(dá)祥楣。Either函子內(nèi)部有兩個(gè)值:左值(left)和右值(right)开财。右值是正常情況下使用的值,左值是右值不存在時(shí)使用的默認(rèn)值误褪。
class Either extends Functor{
constructor(left,right){
this.left=left;
this.right=right;
}
map(f){
//右值存在變右值责鳍,否則變左值
return this.right?Either.of(this.left,f(this.right)):Either.of(f(this.left),this.right);
}
}
Either.of=function(left,right){
return new Either(left,right);
}
var addOne=function(x){
return x+1;
}
Either.of(5,6).map(addOne); //Either(5,7);
Either.of(1,null).map(addOne); //Either(2);
Either
//右值中有address這個(gè)屬性,則覆蓋原來(lái)的xxx兽间,否則使用默認(rèn)的xxx
.of({address:'xxx'},currentUser.address) .map(updateField);
es5寫(xiě)法
錯(cuò)誤處理薇搁、Either
var Left=function(x){
this.__value=x;
}
var Rigth=function(x){
this.__value=x;
}
Left.of=function(x){
return new Left(x);
}
Right.of=function(x){
return new Right(x);
}
Left.prototype.map=functin(f){
return this;
}
Right.prototype.map=function(f){
return Right.of(f(this.__value));
}
Left和Right唯一的區(qū)別就在于map方法的實(shí)現(xiàn),Right.map的行為和我們之前提到的map函數(shù)一樣渡八。但是Left.map就很不同了:它不會(huì)對(duì)容器做任何事情啃洋,只是很簡(jiǎn)單地把這個(gè)容器拿進(jìn)來(lái)又扔出去。這個(gè)特性意味著屎鳍,Left可以用來(lái)傳遞一個(gè)錯(cuò)誤消息宏娄。
var getAge = user => user.age ? Right.of(user.age):Left.of("Error");;
getAge({name:'stark',age:'21'}).map(age=>'Age is '+age);
//Right('Age is 21');
getAge({name:'stark'}).map({age=>'Age is '+age});
//Left('Error');
Left 可以讓調(diào)用鏈中任意一環(huán)的錯(cuò)誤立即返回到調(diào)用鏈的尾部,這給我們錯(cuò)誤處理帶來(lái)了很大的方便逮壁,再也不用一層又一層的Try/catch
AP因子
函子里面包含的值孵坚,完全可能是函數(shù)。我們可以想象這樣一種情況窥淆,一個(gè)函子的值是數(shù)值卖宠,另一個(gè)函子的值是函子。
class Ap extends Functor{
ap(F){
return Ap.of(this.val(F.val));
}
}
Ap.of(addTwo).ap(Functor.of(2));
實(shí)例
function Functor(val){
this.__val=val;
}
Functor.of=function(val){
return new Functor(val);
}
Functor.prototype.map=function(fn){
return Functor.of(fn(this.__val));
}
function addTwo(x){
return x+2;
}
function Ap(val){
Functor.call(this,val);
}
Ap.of=function(val){
return new Ap(val);
}
var __proto=Object.create(Functor.prototype);
__proto.constructor=Ap.prototype.constructor;
Ap.prototype=__proto;
Ap.prototype.ap=function(F){
return Ap.of(this.__val(F.__val));
}
const A=Functor.of(2);
const B=Ap.of(addTwo);
console.log(B.ap(A)); //4
//console.log(B.ap(A).ap(A));此時(shí)會(huì)報(bào)錯(cuò)忧饭,B.ap(A)其中A不是個(gè)函數(shù)
IO
真正的程序總要去接觸骯臟的世界
function readLoaclStorage(){
return window.localStorage;
}
Io跟前面那幾個(gè)Functor不同的地方在于扛伍,他的__value是一個(gè)函數(shù)。它把不純的操作(比如IO词裤、網(wǎng)絡(luò)請(qǐng)求刺洒、DOM)包裹到一個(gè)函數(shù)內(nèi)鳖宾,從而延遲這個(gè)操作的執(zhí)行。所以我們認(rèn)為逆航,IO包含的是被包裹的操作的返回值鼎文。
IO其實(shí)也算是惰性求值
IO負(fù)責(zé)了調(diào)用鏈積累了很多很多不純的操作,帶來(lái)的復(fù)雜性和不可維護(hù)性因俐。
import _ from 'lodash';
var compose=_.flowRight;
var IO=function(f){
this._value=f;
}
IO.of=x=>new IO(_=>x);
IO.prototype.map=function(f){
return new IO(compose(f,this.__value));
}
Monad
Monad就是一種設(shè)計(jì)模式拇惋,表示將一個(gè)運(yùn)算過(guò)程,通過(guò)函數(shù)拆解成互相連接的多個(gè)步驟抹剩。你只要提供下一步運(yùn)算所需的函數(shù)蚤假,整個(gè)運(yùn)算就會(huì)自動(dòng)進(jìn)行下去。
Promise就是一種Monad
Monad糖我們避開(kāi)了嵌套地獄吧兔,可以輕松地進(jìn)行深度嵌套的函數(shù)式編程磷仰,比如IO和其它異步任務(wù)
Maybe.of(
Maybe.of(
Maybe.of({name:'Mulburry',number:99})
)
)
class Monad extends Functor{
join(){
return this.val;
}
flatMap(f){
return this.map(f).join();
}
}
Monad 函子的作用是,總是返回一個(gè)單層的函子境蔼。它有一個(gè)flatMap方法灶平,與map方法作用相同,唯一的區(qū)別是如果生成了一個(gè)嵌套函子箍土,他會(huì)取出后這內(nèi)部的值逢享,保證返回的永遠(yuǎn)是一個(gè)單層的容器,不會(huì)出現(xiàn)嵌套的情況吴藻。
如果函數(shù)f返回的是一個(gè)函子瞒爬,那么this.map(f)就會(huì)生成一個(gè)嵌套的函子。所以沟堡,join方法保證了flatMap方法總是返回一個(gè)單層的函子侧但。這意味著嵌套的漢子會(huì)被鋪平(flatMap)