轉眼之間已入五月,本身畢業也立刻有三年了。大學計算機系的同窗大多都在北京混跡,你們爲了升職加薪,娶媳婦買房,熬夜加班跟上線,出差pk腦殘客戶。同窗聚會時有很多兄弟已經體重飆升,開始關注13號地鐵線上鋪天蓋地的植髮廣告。都說25歲是一個男人的分界線,以前是越活越精緻,日後是越活越糙。如今體會到了。父母開始老去,本身尚一無全部,攢的錢不夠買一平米的房。昨天和一哥們擼串,我問:有啥打算?哥們吞了幾口羊肉串,喝了一口啤酒,說:存點錢吧,而後回家。javascript
說實話,我之前的想法也同樣。奈何來北京容易,想走卻很難。清明節回太原一趟,總覺的路上太過於寂靜,你們走路速度太慢,商店關門太早,居然有些許不適應。兀的發覺,北京肉體雖然天天很疲憊,但靈魂力量卻修煉的很強。回到昌平的20平出租屋內,心裏暗想,繼續混,混到混不下去爲止。css
以前寫過一篇博文《我在百度作外包》,沒想到許多同窗深有同感。北京的外包羣體是一個很大的社會組織結構,他們須要獲得更多的關注。但生存是社會永恆的主題,外包人員更要懂得,一切以實力說話,沒有技術就沒有發言權。沒有名校背景的同窗進大廠很難,反過來講外包也是另一條路,只不過這條路須要付出更多的努力,機會永遠留給有準備的人。html
這篇博文,做爲我僥倖從外包轉成正式百度員工的記念。前端
這是一個系列博客,但願他能在個人碼農生涯中留下些什麼。java
閒話很少說,這篇文章主要和你們分析下前端的函數式編程思想。綱要以下:git
對於前端,全部的成員是一個集合,變形關係是函數。編程
1..函數式編程(Functional Programming)其實相對於計算機的歷史而言是一個很是古老的概念,甚至早於第一臺計算機的誕生。函數式編程的基礎模型來源於 λ (Lambda x=>x*2)演算,而 λ 演算並不是設計於在計算機上執行,它是在 20 世紀三十年代引入的一套用於研究函數定義、函數應用和遞歸的形式系統。canvas
2.函數式編程不是用函數來編程,也不是傳統的面向過程編程。主旨在於將複雜的函數符合成簡單的函數(計算理論,或者遞歸論,或者拉姆達演算)。運算過程儘可能寫成一系列嵌套的函數調用設計模式
3.JavaScript 是披着 C 外衣的 Lisp。數組
4.真正的火熱是隨着React的高階函數而逐步升溫。
5.函數是一等公民。所謂」第一等公民」(first class),指的是函數與其餘數據類型同樣,處於平等地位,能夠賦值給其餘變量,也能夠做爲參數,傳入另外一個函數,或者做爲別的函數的返回值。
6.不可改變量。在函數式編程中,咱們一般理解的變量在函數式編程中也被函數代替了:在函數式編程中變量僅僅表明某個表達式。這裏所說的’變量’是不能被修改的。全部的變量只能被賦一次初值。
7.map & reduce他們是最經常使用的函數式編程的方法。
將上面的概念簡述一下:
1. 函數是」第一等公民」
2. 只用」表達式",不用"語句"
3. 沒有」反作用"
4. 不修改狀態
5. 引用透明(函數運行只靠參數)
•純函數
•函數的柯里化
•函數組合
•Point Free
•聲明式與命令式代碼
•核心概念
什麼是純函數呢?
對於相同的輸入,永遠會獲得相同的輸出,並且沒有任何可觀察的反作用,也不依賴外部環境的狀態的函數,叫作純函數。
舉個栗子:
var xs = [1,2,3,4,5];// Array.slice是純函數,由於它沒有反作用,對於固定的輸入,輸出老是固定的 xs.slice(0,3); xs.slice(0,3); xs.splice(0,3);// Array.splice會對原array形成影響,因此不純 xs.splice(0,3);
傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數。
咱們有這樣一個函數checkage:
var min = 18;
var checkage = age => age > min;
這個函數並不純,checkage 不只取決於 age還有外部依賴的變量 min。 純的 checkage 把關鍵數字 18 硬編碼在函數內部,擴展性比較差,柯里化優雅的函數式解決。
var checkage = min => (age => age > min);
var checkage18 = checkage(18); // 先將18做爲參數,去調用此函數,返回一個函數age => age > 18;
checkage18(20);// 第二步,上面返回的函數去處理剩下的參數,即 20 => 20 > 18; return true;
// 柯里化以前 function add(x, y) { return x + y; } add(1, 2) // 3 // 柯里化以後 function addX(y) { return function (x) { return x + y; }; } addX(2)(1) // 3
爲了解決函數嵌套過深,洋蔥代碼:h(g(f(x))),咱們須要用到「函數組合」,咱們一塊兒來用柯里化來改他,讓多個函數像拼積木同樣。
const compose = (f, g) => (x => f(g(x))); var first = arr => arr[0]; var reverse = arr => arr.reverse(); var last = compose(first, reverse); last([1, 2, 3, 4, 5]); // 5
函數組合交換律,相似於乘法交換律:
const f = str => str.toUpperCase().split(' ');
var toUpperCase = word => word.toUpperCase(); var split = x => (str => str.split(x)); var f = compose(split(' '), toUpperCase); f("abcd efgh");
把一些對象自帶的方法轉化成純函數,而後經過函數組合去調用,這種風格可以幫助咱們減小沒必要要的命名,讓代碼保持簡潔和通用。是否是很方便!
在咱們平常業務開發中,寫的代碼絕大多數都爲命令式代碼;
//命令式 let CEOs = []; for (var i = 0; i < companies.length; i++) { CEOs.push(companies[i].CEO) } //聲明式 let CEOs = companies.map(c => c.CEO);
下面咱們再深刻一下,你們注意好好理解吸取:
高階函數,就是把函數當參數,把傳入的函數作一個封裝,而後返回這個封裝函數,達到更高程度的抽象。
//命令式 var add = function (a, b) { return a + b; }; function math(func, array) { return func(array[0], array[1]); } math(add, [1, 2]); // 3
// 不是尾遞歸,沒法優化 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強制使用尾遞歸
咱們看一下遞歸和尾遞歸執行過程:
遞歸:
function sum(n) { if (n === 1) return 1; return n + sum(n - 1); }
sum(5) (5 + sum(4)) (5 + (4 + sum(3))) (5 + (4 + (3 + sum(2)))) (5 + (4 + (3 + (2 + sum(1))))) (5 + (4 + (3 + (2 + 1)))) (5 + (4 + (3 + 3))) (5 + (4 + 6)) (5 + 10) 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
1.函數不只能夠用於同一個範疇之中值的轉換,還能夠用於將一個範疇轉成另外一個範疇。這就涉及到了函子(Functor)。
2.函子是函數式編程裏面最重要的數據類型,也是基本的運算單位和功能單位。它首先是一種範疇,也就是說,是一個容器,包含了值和變形關係。比較特殊的是,它的變形關係能夠依次做用於每個值,將當前容器變造成另外一個容器。
$(...) 返回的對象並非一個原生的 DOM 對象,而是對於原生對象的一種封裝,這在某種意義上就是一個「容器」(但它並不函數式)。
Functor(函子)遵照一些特定規則的容器類型。任何具備map方法的數據結構,均可以看成函子的實現。
Functor 是一個對於函數調用的抽象,咱們賦予容器本身去調用函數的能力。把東西裝進一個容器,只留出一個接口 map 給容器外的函數,map 一個函數時,咱們讓容器本身來運行這個函數,這樣容器就能夠自由地選擇什麼時候何地如何操做這個函數,以至於擁有惰性求值、錯誤處理、異步調用等等很是牛掰的特性。
下面咱們看下函子的代碼實現:
var Container = function (x) { this.__value = x; } // 函數式編程通常約定,函子有一個of方法 Container.of = x => new Container(x); // Container.of(‘abcd’); // 通常約定,函子的標誌就是容器具備map方法。該方法將容器 // 裏面的每個值, 映射到另外一個容器。 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')
class Functor { constructor(val) { this.val = val; } map(f) { return new Functor(f(this.val)); } } (new Functor(2)).map(function (two) { return two + 2; }); // Functor(4)
Functor.of = function (val) { return new Functor(val); }; Functor.of(2).map(function (two) { return two + 2; }); // Functor(4)
下面咱們介紹一些經常使用的函子。
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(原型來自於Haskell,Haskell是通用函數式編程語言)
Functor.of(null).map(function (s) { return s.toUpperCase(); }); // TypeError Maybe.of(null).map(function (s) { return s.toUpperCase(); }); // Maybe(null)
咱們的容器能作的事情太少了,try/catch/throw 並非「純」的,由於它從外部接管了咱們的函數,而且在這個函數出錯時拋棄了它的返回值。Promise 是能夠調用 catch 來集中處理錯誤的。事實上 Either 並不僅是用來作錯誤處理的,它表示了邏輯或。
條件運算if...else是最多見的運算之一,函數式編程裏面,使用 Either 函子表達。Either 函子內部有兩個值:左值(Left)和右值(Right)。右值是正常狀況下使用的值,左值是右值不存在時使用的默認值。
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); };
使用Either函子:
var addOne = function (x) { return x + 1; }; Either.of(5, 6).map(addOne); // Either(5, 7); Either.of(1, null).map(addOne); // Either(2, null); Either .of({ address: 'xxx' }, currentUser.address) .map(updateField);
class Ap extends Functor { ap(F) { return Ap.of(this.val(F.val)); } }
function addOne(x) { return x + 1; }
Ap.of(addOne).ap(Functor.of(1)) // ap函子,讓addOne能夠用後面函子中的val運算 結果爲Ap(2)
class IO extends Monad { map(f) { return IO.of(compose(f, this.__value)) } }
在這裏,咱們提到了Monad,Monad就是一種設計模式,表示將一個運算過程,經過函數拆解成互相鏈接的多個步驟。你只要提供下一步運算所需的函數,整個運算就會自動進行下去。Promise 就是一種 Monad。Monad 讓咱們避開了嵌套地獄,能夠輕鬆地進行深度嵌套的函數式編程,好比IO和其它異步任務。
class Monad extends Functor { join() { return this.val; } flatMap(f) { return this.map(f).join(); } }
關於更多的Monad介紹,能夠移步知乎什麼是Monad。
var clicks = Rx.Observable .fromEvent(document, 'click') .bufferCount(2) .subscribe(x => console.log(x)); // 打印出前2次點擊事件
function main(sources) { const sinks = { DOM: sources.DOM.select('input').events('click') .map(ev => ev.target.checked) .startWith(false) .map(toggled => <div> <input type="checkbox" /> Toggle me <p>{toggled ? 'ON' : 'off'}</p> </div> ) }; return sinks; } const drivers = { DOM: makeDOMDriver('#app') }; run(main, drivers);
var abc = function (a, b, c) { return [a, b, c]; }; var curried = _.curry(abc); curried(1)(2)(3);
function square(n) {
return n * n;
}
var addSquare = _.flowRight(square, _.add); // 相似於上面說的函數組合
addSquare(1, 2);
// => 9
2.知乎 什麼是 Monad (Functional Programming)?