面試官:你好,你有用過函數式編程嗎?
我:函數式?沒有呢?
面試官:那有了解過嗎?
我:額。。。也沒有,貴公司主要使用函數式編程麼?
面試官:嗯,不怎麼使用,但我就是想問javascript
又到了面試的季節,相比不少小夥伴會被面試官一頓連環問,面試造火箭,進去擰螺絲,可想要順利入職仍是得硬着頭皮把火箭造出來才行。html
最近不按期寫一些跟面試相關的知識點,爲大夥的面試打氣加油。前端
這篇咱們說說函數式編程。java
函數式編程是一種編程範式,與面向對象、面向過程都是一種編程的方法論,簡單的說就是如何設計咱們的程序結構。node
函數式的主要思想就是把運算過程寫成一系列的函數調用,好比說面向過程咱們是這麼寫的git
f(msg){
// 分隔msg
...
// 拼接msg
...
// 其餘處理
....
}
複製代碼
函數式就會變成這種形式github
a(msg){...// 分隔msg}
b(msg){...// 拼接msg}
c(msg){...// 其餘處理}
f(msg) = a(msg).b(msg).c(msg)
複製代碼
至關於把本來一個大函數拆解成一個個獨立的小函數,而後經過鏈式調用,把獨立的小函數串聯起來,已達到輸出的結果與本來的大函數一致,有點相似於管道,或者說流水線,上一個工序處理結束,傳給下一個工序,最後輸出成品。面試
函數式編程,其函數必須知足一個基本條件:函數必須是沒有反作用的,不能直接或間接修改除自身外的變量,僅僅是一種數據轉換的行爲。片草叢中過,既不沾花也不惹草。編程
函數式編程有兩個最基本的運算:合成和柯里化。redux
在以前的文章有詳細介紹過函數柯里化,請戳用大白話介紹柯里化函數,咱們在接着看另外一個基本運算-合成
函數合成,英文名叫作 compose
,意思就是一個值要通過多個函數,才能變成另一個值,將這個多個函數合併成一個函數,就是函數的合成,舉個栗子
var name = 'xiaoli'
name = a(name){...}
name = b(name){...}
name = c(name){...}
console.log(name)
複製代碼
name 通過三個函數才最終輸出咱們須要的值,那麼函數合成後,變成以下
var fn = compose(a,b,c)
console.log(fn(name))
複製代碼
這裏引用阮一峯老師的一張圖
函數組合仍是很是好理解的,標準的函數組合還須要知足結合律,在引用阮一峯老師的的圖
意思就是 compose(a,b)
生成的函數也必須是一個純淨的函數,對調用者來講這個生成的函數是透明的,調用者只關心 a和b
的實現便可。
我這裏我要推薦把函數式編程玩的最溜的 redux
,也是這些大佬們把函數式編程在前端圈給推了起來。咱們看代碼
// https://github.com/reduxjs/redux/blob/master/src/compose.js
/** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))). */
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼
是否是很精煉? Array.reduce
大法好啊,巧妙的實現函數嵌套引用。
在看別的函數庫如何實現的,先看看 lodash.js
的實現
/** * Composes a function that returns the result of invoking the given functions * with the `this` binding of the created function, where each successive * invocation is supplied the return value of the previous. * * @since 3.0.0 * @category Util * @param {Function[]} [funcs] The functions to invoke. * @returns {Function} Returns the new composite function. * @see flowRight * @example * * function square(n) { * return n * n * } * * const addSquare = flow([add, square]) * addSquare(1, 2) * // => 9 */
function flow(funcs) {
const length = funcs ? funcs.length : 0
let index = length
while (index--) {
if (typeof funcs[index] != 'function') {
throw new TypeError('Expected a function')
}
}
return function(...args) {
let index = 0
let result = length ? funcs[index].apply(this, args) : args[0]
while (++index < length) {
result = funcs[index].call(this, result)
}
return result
}
}
複製代碼
loadsh 看着要稍微複雜些,但兼容性更高,畢竟有些落後的瀏覽器無法支持reduce
ramda.js
一個更具備函數式表明的函數庫,這個函數庫很是有意思,每一個函數都默認支持柯里化,對酷愛函數式編程的夥伴來講,那就是個大殺器啊,咱們看下它 compose
的實現,註釋略長,爲了方便你們看,我把註釋簡化下
// compose.js
import pipe from './pipe';
import reverse from './reverse';
/** * @func * @category Function * @sig ((y -> z), (x -> y), ..., (o -> p), ((a, b, ..., n) -> o)) -> ((a, b, ..., n) -> z) * @param {...Function} ...functions The functions to compose * @return {Function} * @see R.pipe * @symb R.compose(f, g, h)(a, b) = f(g(h(a, b))) */
export default function compose() {
if (arguments.length === 0) {
throw new Error('compose requires at least one argument');
}
return pipe.apply(this, reverse(arguments));
}
// pipe.js
import _pipe from './internal/_pipe';
import reduce from './reduce';
export default function pipe() {
if (arguments.length === 0) {
throw new Error('pipe requires at least one argument');
}
return _arity(
arguments[0].length,
reduce(_pipe, arguments[0], tail(arguments))
);
}
// 封裝的層次比較多,就不一一展開了
複製代碼
ramda 本身實現了reduce,因此兼容性也是OK。
至於 compose
的函數執行順序是從左到右仍是右到左,這個無非是把執行的順序作個調換。
函數組合的使用場景仍是多的,常見的數據處理,只要修改函數因子就能夠輸入不一樣的結果,不須要修改原有流程。我寫個簡單的小栗子
// 由於我喜歡從左到右的順序執行,因此 reduce 裏的順序稍微調換了下
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => {
return (...args) => b(a(...args)) // 從左到右的順序執行
})
}
function fristName(name){
return name.split(' ')[0]
}
function upperCase(string){
return string.toUpperCase()
}
function reverse(string){
return string.split('').reverse().join('')
}
console.log(compose(fristName,upperCase,reverse)('xiao li')) // OAIX
複製代碼
用起來仍是很是順手的,相信你們基於 compose 寫出不少有意思的代碼。
咱們來看下牛逼的 koa2
框架是怎麼經過 compose
實現的洋蔥模型,我以爲很是巧妙,是koa的精髓部分,部分代碼以下
// https://github.com/koajs/koa/blob/master/lib/application.js
/** * Return a request handler callback * for node's native http server. * * @return {Function} * @api public */
callback() {
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error', this.onerror);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
// https://github.com/koajs/compose/blob/master/index.js
/** * Compose `middleware` returning * a fully valid middleware comprised * of all those which are passed. * * @param {Array} middleware * @return {Function} * @api public */
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/** * @param {Object} context * @return {Promise} * @api public */
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
複製代碼
把 compose
和 curry
一塊兒使用能夠把函數這東東玩的很是靈活,但對編寫函數就會有些要求,如何避免產生反作用,如何設計函數輸入、輸出,如何設計函數的邊界等等。
在某些場景下使用函數式編程,不只能代碼更具擴展性,還能讓本身的代碼看起來逼格更高。
講到這裏,若在面試的時候把上面涉及到的點和栗子大體說出來,面試的這一關那絕對穩啦。