溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

函數(shù)式編程入門實踐(一)

發(fā)布時間:2020-09-30 18:31:53 來源:腳本之家 閱讀:171 作者:蘇里 欄目:web開發(fā)

在文章之前,先和大家講一下對于函數(shù)式編程(Functional Programming, aka. FP)的理解(下文我會用FP指代函數(shù)式編程):

  1. FP需要保證函數(shù)都是純凈的,既不依賴外部的狀態(tài)變量,也不產生副作用?;诖饲疤嵯?,那么純函數(shù)的組合與調用,在時間順序上就不會產生依賴,改變多個函數(shù)的調用順序也不必擔心產生問題,因此也會消滅許多潛在的bug。
  2. 函數(shù)必須有輸入輸出。如果一個函數(shù)缺乏輸入或輸出,那么它其實是一段處理程序procedure而已。
  3. 函數(shù)盡可能的保持功能的單一,如果一個函數(shù)做了多件事情,那么它理論上應當被拆分為多個函數(shù)。
  4. FP的意義之一就是,在適當?shù)臅r機使用聲明式編程,抽象了程序流的控制與表現(xiàn),從理解和維護的角度上會勝于命令式編程。
  5. FP是一種范式,但并不意味這和OOP(面向對象編程)沖突,兩者當然是可以和諧共存的。個人認為 React 其實就是一個很好的栗子~
  6. Javascript的函數(shù)一等公民以及閉包的特性,決定了Javascript的確是適合施展FP的舞臺

理解閉包

閉包對于 Javascript 來說,當然十分重要。然而對于函數(shù)式編程來說,這更加是必不可少的,必須掌握的概念,閉包的定義如下:

Closure is when a function remembers and accesses variables from outside of its own scope, even when that function is executed in a different scope.

相信大部分同學都對閉包有不錯的理解,但是由于對FP的學習十分重要。接下來我還是會啰嗦的帶大家過一遍。閉包就是能夠讀取其他函數(shù)內部變量的函數(shù)

簡單示例如下

// Closure demo
function cube(x) {
 let z = 1;
 return function larger(y) {
 return x * y * z++;
 };
}

const makeCube = cube(10);
console.log(makeCube(5)); // 50
console.log(makeCube(5)); // 100

那么有沒有想過在函數(shù)makeCube,或者也可以說是函數(shù)larger是怎么記住原本不屬于自己作用域的變量x和z的呢?在控制臺查看makeCube.prototype,點開會發(fā)現(xiàn)原來是有個[[Scopes]]這個內置屬性里的Closure(cube)記住了函數(shù)larger返回時記住的變量x和z。如果多嵌套幾層函數(shù),也會發(fā)現(xiàn)多幾個Closure(name)在[[Scopes]]的Scopes[]數(shù)組里,按序查找變量。

函數(shù)式編程入門實踐(一)

再看下圖測試代碼:

function cube(x) {
 return function wrapper(y) {
 let z = 1;
 return function larger() {
  return x * y * z++;
 };
 }
}

const makeCubeY = cube(10);
const makeCube = makeCubeY(5);
const $__VAR1__ = '1. This var is just for test.';
let $__VAR2__ = '2. This var is just for test.';
var $__VAR3__ = '3. This var is just for test.';
console.log(makeCubeY.prototype, makeCube.prototype);
console.log(makeCube()); // 50
console.log(makeCube()); // 100

打印makeCubeY.prototype:

函數(shù)式編程入門實踐(一)

打印makeCube.prototype:

 函數(shù)式編程入門實踐(一)

通過這幾個實驗可以從另一個角度去理解Javascript中閉包,一個閉包是怎么去查找不是自己作用域的變量呢?makeCube函數(shù)分別從[[Scopes]]中的Closure(wrapper)里找到變量y、z,Closure(cube)里找到變量x。至于全局let、const聲明的變量放在了Script里,全局var聲明的變量放在了Global里。

在學習FP前,理解閉包是尤為重要的~ 因為事實上大量的FP工具函數(shù)都使用了閉包這個特性。

工具函數(shù)

unary

const unary = fn => arg => fn(arg);

一元函數(shù),應用于當只想在某個函數(shù)上傳遞一個參數(shù)情況下使用。嘗試考慮以下場景:

console.log(['1', '2', '3'].map(parseInt)); // [1, NaN, NaN]
console.log(['1', '2', '3'].map(unary(parseInt))); // [1, 2, 3]

parseInt(string, radix)接收兩個參數(shù),而map函數(shù)中接收的回調函數(shù)callback(currentValue[, index[, array]]),第二個參數(shù)是index,此時如果parseInt的使用就是錯誤的。當然除了Array.prototype.map,大量內置的數(shù)組方法中的回調函數(shù)中都不止傳遞一個參數(shù),如果存在適用的只需要第一個參數(shù)的場景,unary函數(shù)就發(fā)揮了它的價值,無需修改函數(shù),優(yōu)雅簡潔地就接入了。(對于unary函數(shù),fn就是閉包記憶的變量數(shù)據(jù))

identity

const identity = v => v;

有同學會看到identity函數(shù)會覺得莫名其妙?是干嘛的?我第一眼看到也很迷惑?但是考慮以下場景:

console.log([false, 1, 2, 0, '5', true].filter( identity )); // [1, 2, "5", true]
console.log([false, 0].some( identity )); // false
console.log([-2, 1, '3'].every( identity )); // true

怎么樣?眼前一亮吧,沒想到identity函數(shù)原來深藏不露,事實上雖然identity返回了原值,但是在這些函數(shù)中Javascript會對返回的值進行類型裝換,變成了布爾值。比如filter函數(shù)。我們可以看MDN定義filter描述如下(看標粗的那一句)。

filter() calls a provided callback function once for each element in an array, and constructs a new array of all the values for which callback returns a value that coerces to true.

constant

const constant = v => () => v;

同樣,這個函數(shù)...乍一看,也不知道具體有什么用。但是考慮場景如下:

onst p1 = new Promise((resolve, reject) => {
 setTimeout(() => {
 resolve('Hello!');
 }, 200);
});
p1.then(() => 'Hi').then(console.log); // Hi!
p1.then(constant('Hi')).then(console.log); // Hi!
p1.then('Hi').then(console.log); // Hello!

由于Promise.prototype.then只接受函數(shù),如果我僅僅只需要傳遞一個值時,那么constant便會提供這種便利。當然這個并沒有什么功能上的提升,但是的確提高了可閱讀性,也是函數(shù)式編程的一個優(yōu)點。

spreadArgs & gatherArgs

const spreadArgs = fn => argsArr => fn( ...argsArr );
const gatherArgs = fn => (...argsArr) => fn( argsArr );

嗯這兩個函數(shù)見名知義。分別用于展開一個函數(shù)的所有參數(shù)和收集一個函數(shù)所有參數(shù),這兩個函數(shù)明顯對立,那么它們的應用場景又是什么呢?

spreadArgs函數(shù)示例如下:

function cube(x, y, z) {
 return x * y * z;
}

function make(fn, points) {
 return fn(points);
}

console.log(make(cube, [3, 4, 5])); // NaN
console.log(make(spreadArgs(cube), [3, 4, 5])); // 60

gatherArgs函數(shù)示例如下:

function combineFirstTwo([v1, v2]) {
 return v1 + v2;
}

console.log([1, 2, 3, 4, 5].reduce(combineFirstTwo)); // Uncaught TypeError
console.log([1, 2, 3, 4, 5].reduce(gatherArgs(combineFirstTwo))); // 15

看完以上代碼,簡單的兩個工具函數(shù),輕易的做到了對一個函數(shù)的轉換,從而使其適用于另一個場景。如果從此應該可以瞥見函數(shù)式編程的一點點魅力,那么下面的兩個函數(shù)將給大家?guī)砀嗟捏@喜。

partial & curry

const partial = (fn, ...presetArgs) => (...laterArgs) =>
 fn(...presetArgs, ...laterArgs);
 
const curry = (fn, arity = fn.length, nextCurried) =>
 (nextCurried = prevArgs => nextArg => {
 const args = [...prevArgs, nextArg];

 if (args.length >= arity) {
  return fn(...args);
 } else {
  return nextCurried(args);
 }
 })([]);

相信大家對函數(shù)柯里化應該或多或少有點了解。維基百科定義:

在計算機科學中,柯里化(英語:Currying),又譯為卡瑞化或加里化,是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結果的新函數(shù)的技術。

當然得益于閉包的強大威力,柯里化這個武器得以誕生于Javascript世界。請大家先精讀以上關于partiel、curry函數(shù)的代碼。

喝一杯咖啡~

先模擬一個ajax函數(shù)如下:

function ajax(url, params, callback) {
 setTimeout(() => {
 callback(
  `GET ${url} \nparams: ${params} \ndata: Hello! ${params} `
 );
 });
}

考慮partial使用場景如下:

const fetchPerson = partial( ajax, "http://some.api/person" );

fetchPerson('Teddy Bear', console.log);
/*
GET http://some.api/person 
params: Teddy Bear 
data: Hello! Teddy Bear 
*/

考慮curry使用場景如下:

const fetchPerson = curry(ajax)('http://some.api/person');
const fetchUncleBarney = fetchPerson('Uncle Barney');

fetchUncleBarney(console.log);
/*
GET http://some.api/person 
params: Uncle Barney 
data: Hello! Uncle Barney 
*/

partial和curry函數(shù)功能相似,但又有具體的不同應用場景,但總體來說curry會比partial更自動化一點。
但是!相信看完示例的同學又會有一連串問號?為什么好好地參數(shù)不一次性傳入,而非要分開多次傳入這么麻煩?原因如下:

  1. 最首要的原因是partial和curry函數(shù)都允許我們通過參數(shù)控制將一個函數(shù)的調用在時間和空間上分開了。傳統(tǒng)函數(shù)需要一次性將參數(shù)湊齊才能調用,但是有時候我們可以提前預置部分參數(shù),在最終需要觸發(fā)此函數(shù)時,才將剩余參數(shù)傳入。這時候partial和curry就會變得十分有用。
  2. partial和curry的存在讓函數(shù)組合(compose)會更加便利。(函數(shù)組合也計劃之后和大家分享,這里就不詳細說了)。
  3. 當然最重要是也提升了可閱讀性!一開始可能不這么以為,但是如果你實踐操作感受之后,也許會改觀。

P.S. 關于函數(shù)式編程的實踐,大家可以使用lodash/fp模塊進行入門實踐。

一些思考
因為我也是函數(shù)式編程的初學者,如有不正確的地方,歡迎大家糾正~

接下來還是會繼續(xù)整理FP的學習資料,學習實踐,連載一些我對于函數(shù)式編程的學習與思考,希望和大家一起進步~

謝謝大家(●´∀`●)~

以上所述是小編給大家介紹的Javascript函數(shù)式編程詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網(wǎng)站的支持!

 

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI