您好,登錄后才能下訂單哦!
在文章之前,先和大家講一下對于函數式編程(Functional Programming, aka. FP)的理解(下文我會用FP指代函數式編程):
理解閉包
閉包對于 Javascript 來說,當然十分重要。然而對于函數式編程來說,這更加是必不可少的,必須掌握的概念,閉包的定義如下:
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的學習十分重要。接下來我還是會啰嗦的帶大家過一遍。閉包就是能夠讀取其他函數內部變量的函數
簡單示例如下
// 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
那么有沒有想過在函數makeCube,或者也可以說是函數larger是怎么記住原本不屬于自己作用域的變量x和z的呢?在控制臺查看makeCube.prototype,點開會發現原來是有個[[Scopes]]這個內置屬性里的Closure(cube)記住了函數larger返回時記住的變量x和z。如果多嵌套幾層函數,也會發現多幾個Closure(name)在[[Scopes]]的Scopes[]數組里,按序查找變量。
再看下圖測試代碼:
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:
打印makeCube.prototype:
通過這幾個實驗可以從另一個角度去理解Javascript中閉包,一個閉包是怎么去查找不是自己作用域的變量呢?makeCube函數分別從[[Scopes]]中的Closure(wrapper)里找到變量y、z,Closure(cube)里找到變量x。至于全局let、const聲明的變量放在了Script里,全局var聲明的變量放在了Global里。
在學習FP前,理解閉包是尤為重要的~ 因為事實上大量的FP工具函數都使用了閉包這個特性。
工具函數
unary
const unary = fn => arg => fn(arg);
一元函數,應用于當只想在某個函數上傳遞一個參數情況下使用。嘗試考慮以下場景:
console.log(['1', '2', '3'].map(parseInt)); // [1, NaN, NaN] console.log(['1', '2', '3'].map(unary(parseInt))); // [1, 2, 3]
parseInt(string, radix)接收兩個參數,而map函數中接收的回調函數callback(currentValue[, index[, array]]),第二個參數是index,此時如果parseInt的使用就是錯誤的。當然除了Array.prototype.map,大量內置的數組方法中的回調函數中都不止傳遞一個參數,如果存在適用的只需要第一個參數的場景,unary函數就發揮了它的價值,無需修改函數,優雅簡潔地就接入了。(對于unary函數,fn就是閉包記憶的變量數據)
identity
const identity = v => v;
有同學會看到identity函數會覺得莫名其妙?是干嘛的?我第一眼看到也很迷惑?但是考慮以下場景:
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函數原來深藏不露,事實上雖然identity返回了原值,但是在這些函數中Javascript會對返回的值進行類型裝換,變成了布爾值。比如filter函數。我們可以看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;
同樣,這個函數...乍一看,也不知道具體有什么用。但是考慮場景如下:
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只接受函數,如果我僅僅只需要傳遞一個值時,那么constant便會提供這種便利。當然這個并沒有什么功能上的提升,但是的確提高了可閱讀性,也是函數式編程的一個優點。
spreadArgs & gatherArgs
const spreadArgs = fn => argsArr => fn( ...argsArr ); const gatherArgs = fn => (...argsArr) => fn( argsArr );
嗯這兩個函數見名知義。分別用于展開一個函數的所有參數和收集一個函數所有參數,這兩個函數明顯對立,那么它們的應用場景又是什么呢?
spreadArgs函數示例如下:
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函數示例如下:
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
看完以上代碼,簡單的兩個工具函數,輕易的做到了對一個函數的轉換,從而使其適用于另一個場景。如果從此應該可以瞥見函數式編程的一點點魅力,那么下面的兩個函數將給大家帶來更多的驚喜。
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); } })([]);
相信大家對函數柯里化應該或多或少有點了解。維基百科定義:
在計算機科學中,柯里化(英語:Currying),又譯為卡瑞化或加里化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并且返回接受余下的參數而且返回結果的新函數的技術。
當然得益于閉包的強大威力,柯里化這個武器得以誕生于Javascript世界。請大家先精讀以上關于partiel、curry函數的代碼。
喝一杯咖啡~
先模擬一個ajax函數如下:
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函數功能相似,但又有具體的不同應用場景,但總體來說curry會比partial更自動化一點。
但是!相信看完示例的同學又會有一連串問號?為什么好好地參數不一次性傳入,而非要分開多次傳入這么麻煩?原因如下:
P.S. 關于函數式編程的實踐,大家可以使用lodash/fp模塊進行入門實踐。
一些思考
因為我也是函數式編程的初學者,如有不正確的地方,歡迎大家糾正~
接下來還是會繼續整理FP的學習資料,學習實踐,連載一些我對于函數式編程的學習與思考,希望和大家一起進步~
謝謝大家(●´∀`●)~
以上所述是小編給大家介紹的Javascript函數式編程詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。