純函數(shù)系谐, 高階函數(shù),函數(shù)組合盏混,函數(shù)柯里化蔚鸥,偏函數(shù)惜论,惰性載入函數(shù),緩存函數(shù)
這些概念在函數(shù)編程中真的是太常見了止喷,尤其是很多類庫實(shí)現(xiàn)或者組件封裝都會用到這些函數(shù)編程技巧馆类。
比如React-redux中的connect方法,React中的高階組件其實(shí)都或多或少用到了上述一些函數(shù)編程技巧弹谁。剛好最近有幸看到一篇關(guān)于這方面的文章乾巧,記錄一下。
在JS中预愤,函數(shù)總是被稱為一等公民沟于,那到底為什么會被稱為一等公民呢?主要是因?yàn)橹部担贘S中函數(shù)可以作為普通變量一樣使用旷太,可以作為函數(shù)的參數(shù),可以被賦值销睁,可以作為函數(shù)的return值供璧。這樣就導(dǎo)致了函數(shù)在JS中具有極其靈活的用法,因此也有了更加強(qiáng)大的功能冻记。
那下面就介紹下上面所說的這些內(nèi)容睡毒,在閱讀別人代碼或者自己封裝一些方法時都會很有用,最重要的是面試的時候被面試官問到了冗栗,不至于完全不知道而尷尷尬尬演顾。
1. 純函數(shù)
一個函數(shù)的返回結(jié)果只依賴于它的參數(shù),并且在執(zhí)行過程里面沒有副作用隅居,我們就把這個函數(shù)叫做純函數(shù)
重點(diǎn)有兩點(diǎn):一是返回結(jié)果只依賴它的參數(shù)钠至,二是執(zhí)行過程中沒有副作用。
執(zhí)行過程沒有副作用是指军浆,不會對函數(shù)外面的變量造成任何影響棕洋。
let num = 0;
// bar不是純函數(shù),因?yàn)榉祷刂狄蕾嚵送獠孔兞縩um
function bar(a, b) {
return a+b+num;
}
// laa不是純函數(shù)乒融,執(zhí)行過程中產(chǎn)生副作用,影響外面num變量
function laa(a, b) {
num++;
return a+b;
}
// foo是純函數(shù)
function foo(a, b) {
return a+b;
}
看一下上面的例子就ok了摄悯。使用純函數(shù)的原因是因?yàn)樗奶攸c(diǎn)是比較靠譜赞季,接收相同的參數(shù),就一定能輸出相同的值奢驯,這樣的程序易于調(diào)試申钩,不易出現(xiàn)莫名其妙的問題。
2. 高階函數(shù)
高階函數(shù)有兩種形式:其一是函數(shù)的參數(shù)是另一個函數(shù)(回調(diào)函數(shù))其二是函數(shù)的返回值是一個函數(shù)瘪阁。
函數(shù)參數(shù)為一個函數(shù)撒遣,可以理解成回調(diào)函數(shù)就是高階函數(shù)的一種邮偎,這樣理解我覺得沒有什么錯∫謇瑁可以參考知乎回調(diào)函數(shù)和高階函數(shù)的區(qū)別禾进?
提問下的回答。
ES6版本常見的數(shù)組的方法們map
filter
reduce
等等都是高階函數(shù)廉涕。
函數(shù)的返回值是一個函數(shù)泻云,舉個例子,比如防抖(debouce)函數(shù)和節(jié)流(throttle)函數(shù)
借助下lodash的例子看下
// 避免窗口在變動時出現(xiàn)昂貴的計算開銷狐蜕。
jQuery(window).on('resize', _.debounce(calculateLayout, 150));
// 當(dāng)點(diǎn)擊時 `sendMail` 隨后就被調(diào)用宠纯。
jQuery(element).on('click', _.debounce(sendMail, 300, {
'leading': true,
'trailing': false
}));
接收一個或多個函數(shù)作為輸入,經(jīng)過加工最終輸出一個新的函數(shù)层释,中間可以定義一些其他邏輯婆瓜。比如再看個小例子:
function Eat(a,b){ //核心業(yè)務(wù)代碼
console.log(a,b)
}
Function.prototype.before = function(callback){ //高階函數(shù)
return (...args)=>{ //使用rest運(yùn)算符接收
callback();
this(...args); //使用展開運(yùn)算符傳入
}
}
let beforeEat = Eat.before(function(){ //自己擴(kuò)展業(yè)務(wù)代碼
console.log("before eat")
})
beforeEat("米飯","牛肉") //傳參
可以用來擴(kuò)展函數(shù)功能。
3. 函數(shù)組合
函數(shù)組合就是將功能單一的函數(shù)贡羔,進(jìn)行組合返回一個功能更加強(qiáng)大的函數(shù)勃救。(如果看過鎧甲勇士的話,想象一下金木水火土合成帝皇俠那種感覺治力,如果沒看過就算了)
為了降低耦合性蒙秒,我們封裝的函數(shù)功能性比較單一,便于在不同的場景下使用宵统。比如如下函數(shù):
function lowerCase(str) {
return str.toLowerCase();
}
function upperCase(str){
return str.toUpperCase();
}
function trim(str) {
return str.trim();
}
三個函數(shù)分別代表三個功能晕讲,當(dāng)有一天忽然需要轉(zhuǎn)換小寫和去除字符串的頭尾空格的函數(shù),比如lowerCaseAndtrim方法這時候可以實(shí)現(xiàn)一個compose方法如下調(diào)用马澈。
let lowerCaseAndtrim = compost(lowerCase, trim);
lowerCaseAndtrim(' JaVascRipt ');
最終目標(biāo)輸出 javascript;
接下來實(shí)現(xiàn)一下compose函數(shù)
思路:compose函數(shù)返回值是一個函數(shù)
function compose(...funcs) {
return function(x) {
let result = x;
for(let i = 0; i < funcs.length; i++) {
result = funcs[i](result)
}
return result;
}
}
遍歷執(zhí)行compose方法接收的每一個方法瓢省,將目標(biāo)參數(shù)逐一交給每個函數(shù)執(zhí)行最終返回。
借助 Array.prototype.reduce實(shí)現(xiàn)
function compose(...funcs) {
return (x) => {
return funcs.reduce((prev, next) => {
return next(prev);
}, x)
}
}
4. 函數(shù)柯里化
函數(shù)柯里化是一種可以實(shí)現(xiàn)函數(shù)多參變單參的方式痊班,在柯里化的過程總勤婚,將一個帶有多個參數(shù)的函數(shù),轉(zhuǎn)換為帶有一個參數(shù)的一系列的嵌套函數(shù)涤伐。它返回一個新函數(shù)馒胆,這個新函數(shù)期望傳入下一個參數(shù)。當(dāng)接收足夠的參數(shù)后凝果,會自動執(zhí)行原函數(shù)
function add(a, b, c) {
return a+b+c;
}
let addCurry = curry(add) // curry就是函數(shù)實(shí)現(xiàn)柯里化的一個方法
addCurry(1)(2)(3); // 本來應(yīng)該add(1,2,3)調(diào)用的 進(jìn)行柯里化之后就可以addCurry(1)(2)(3) 調(diào)用了
關(guān)于函數(shù)柯里化的討論可以參考柯里化對函數(shù)式編程有何意義祝迂?
柯里化實(shí)現(xiàn):
思路:返回值是一個函數(shù),需要根據(jù)原函數(shù)參數(shù)是否等于調(diào)用時傳入的參數(shù)個數(shù)來判斷是否相等器净,相等直接傳入?yún)?shù)執(zhí)行原函數(shù)型雳,否則返回函數(shù),繼續(xù)接收參數(shù),并遞歸判斷原函數(shù)參數(shù)是否等于調(diào)用時傳入的參數(shù)個數(shù)邏輯纠俭。
function _curry(func) {
return function curried(...args) {
if(func.length === args.length) {
return func.apply(this, args)
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
}
}
5. 偏函數(shù)
偏函數(shù)指的是固定函數(shù)某些參數(shù)沿量,從而產(chǎn)生更小元的函數(shù)。元指的是函數(shù)的參數(shù)個數(shù)冤荆。
比如封裝函數(shù)的時候函數(shù)一共有三個參數(shù)朴则,第一個參數(shù)是固定不變的,后面的參數(shù)是變化的那么就可以使用偏函數(shù)匙赞。當(dāng)然也可以固定前兩個參數(shù)佛掖。
function add(int, a, b){
return int+a+b;
}
let partialAdd = partial(add, 10);
partialAdd(1,2); // 輸出13
partialAdd(3,4); // 輸出17
上面是偏函數(shù)使用形式,現(xiàn)在實(shí)現(xiàn)一個偏函數(shù)
function partial(func, ...args){
return (...args2) => {
return func.apply(this, [...args, ...args2]);
}
}
6. 惰性載入函數(shù)
惰性載入函數(shù)旨在提升代碼性能涌庭。比如函數(shù)需要根據(jù)不同的判斷結(jié)果返回不同的值芥被,每一次執(zhí)行函數(shù)的時候就需要判斷一次,判斷的邏輯如果復(fù)雜的話坐榆,那么對于性能消耗就比較大拴魄。尤其是當(dāng)判斷條件是固定的時候使用惰性載入函數(shù)是非常適合的。
比如JS代碼判斷運(yùn)行平臺是安卓還是IOS
let isIos = 1;
function engin() {
if(isIos){
console.log('ios');
// ios邏輯
} else {
console.log('android');
// 安卓邏輯
}
}
engin();
engin();
執(zhí)行這個engin方法的時候次都要去走判斷平臺邏輯席镀,性能肯定是消耗滴匹中,我們要的是當(dāng)?shù)谝淮螆?zhí)行的時候判斷下平臺邏輯,之后每一次執(zhí)行就不需要去判斷了豪诲,因?yàn)槠脚_是不變的顶捷,可以節(jié)省一些開銷。
惰性載入函數(shù)的實(shí)現(xiàn)1:利用命名覆蓋
const isIos = 1;
let engin = function() {
console.log('這里只執(zhí)行一次哦'); // 注意這里
if(isIos){
engin = function() {
console.log('ios');
// ios邏輯
}
} else {
engin = function() {
console.log('android');
// 安卓邏輯
}
}
return engin();
}
engin(); // ios
engin(); // ios
engin(); // ios
最終ios輸出3次屎篱,但是上面的console只輸出一次服赎。
惰性載入函數(shù)的實(shí)現(xiàn)2:利用立即執(zhí)行函數(shù)
const isIos = 1;
let engin = (function() {
if(isIos){
return function() {
console.log('ios');
// ios邏輯
}
} else {
return function() {
console.log('android');
// 安卓邏輯
}
}
})();
engin();
engin();
JS文件加載時直接執(zhí)行一次,之后每次調(diào)用都是執(zhí)行判斷之后的邏輯交播。對于性能優(yōu)化還是比較有用的重虑。
7. 緩存函數(shù)
緩存函數(shù),指的是根據(jù)函數(shù)參數(shù)將函數(shù)執(zhí)行的結(jié)果緩存起來秦士,當(dāng)下次再調(diào)用的時候不用去計算就可以直接拿到結(jié)果缺厉,很明顯,這是一個空間換時間的做法隧土。
直接寫實(shí)現(xiàn)了提针。
function memorize(func) {
let cache = Object.create(null);
return (...args) => {
let key = JSON.stringify(args);
return cache[key] || (cache[key] = fn.apply(this, args));
}
}
function add(a,b) {
console.log('lala');
return a+b;
}
let d = memorize(add);
console.log(d(1,2)); // 計算獲取
console.log(d(1,2)); // 從緩存獲取
console.log(d(1,3)); // 計算獲取
console.log(d(1,3)); // 從緩存獲取
當(dāng)?shù)诙我韵嗤瑓?shù)去計算的時候,默認(rèn)首先從緩存中找次洼,如果找到對應(yīng)的值关贵,那么直接返回,不參與計算卖毁。這樣程序運(yùn)行速度會比較快,但是,比較消耗內(nèi)存亥啦。
以上就是一些關(guān)于函數(shù)編程中的一些技巧使用炭剪。