簡單提一下,一等公民都具有如下特性:javascript
寫過JS的人確定都知道函數是能知足上述的特性。前端
函數式編程是一種編程範式,其中函數定義的是表達式樹,每一個表達式都返回一個值,而不是改變程序狀態的命令語句。由於函數是 JavaScript 的一等公民,因此能夠吧函數做爲其餘函數的參數或者返回值,這樣就能夠將其中小功能以模塊化的方式組合在一塊兒。java
能夠經過禁止更改外部狀態和數據來定義純函數,純函數是隻依賴實際參數,無論任何全局或者局部的狀態。既輸入相同的參數,輸出的內容永遠都是同樣的。git
const sum = (x, y) => x + y;
sum(1, 2) // 3
複製代碼
原生例子編程
let arr = [1, 2, 3];
arr.slice(0,1); // [1]
arr.slice(0,1); // [1]
arr // [1, 2, 3]
arr.splice(0,1); // [1]
arr.splice(0,1); // [2]
arr // [3]
複製代碼
由上可見數組的 slice
方法是純函數,不會改變原對象;splice
改變了原對象,致使每次操做自身都發生變化,因此不是純函數。小程序
使用純函數時咱們發現程序出現了與預期不符的狀況,也就是輸出的值不是想要得值,那麼只用檢查輸入的參數就OK。數組
上面提到純函數,輸入值不變,那麼輸出值也會不變,這裏就能夠對比輸入的參數是否發生變化,來決定是否要從新渲染來實現一個緩存優化。緩存
const memoize = f => { // 緩存函數
let cache = {};
return a => { // 這裏爲了方便 就只接收一個參數了
const prevValue = cache[a];
if (prevValue) {
console.log(`cache ${prevValue}`);
return prevValue;
}
return cache[a] = f(a); // 緩存純函數的值
}
}
const double = x => x * 2; // 運算函數(純函數)
const f = memoize(double);
f(2); // 4
f(2); // cache 4
f(8); // 16
f(8); // cache 16
複製代碼
柯里化是吧接收多個參數的函數變成接收單一參數的函數,剩下的參數再經過返回的函數來進行接收。 簡單理解就是把函數拆的更細,返回的函數依賴第一個參數進行計算,能夠縮小適用範圍,建立一個針對性更強的函數。微信
const sum = x => y => x + y;
sum(1)(2); // 3
複製代碼
將 sum
函數轉爲只接收一個參數,並返回一個函數,再次調用將獲得結果。 或許有人以爲這樣寫純屬蛋疼,那麼就舉一個業務中會用到的一個例子。咱們須要一個能夠將金額轉爲千分位的函數,可是金額的單位不定,有多是分,有多是角等。網絡
const formatMoney = (money, step) => {
let str = (money / step).toFixed(2); // 兩位小數
const index = str.indexOf('.');
if (index > 3) {
const start = str.substring(0, index).replace(/\B(?=(?:\d{3})+$)/g, ','); // 增長千分位符號
return start + str.substring(index);
};
return str;
}
formatMoney(1000000, 100); // 分 10,000.00
formatMoney(123456, 100); // 分 1,234.56
formatMoney(123456, 10); // 角 12,345.60
複製代碼
傳入兩個參數 (money, step)
金額和單位,在函數中依據單位來實時金額。可是每次都要傳單位,形成重複的代碼。
咱們能夠將上述方法轉爲柯里化函數:
const formatMoney = step => money => {
let str = (money / step).toFixed(2); // 兩位小數
const index = str.indexOf('.');
if (index > 3) {
const start = str.substring(0, index).replace(/\B(?=(?:\d{3})+$)/g, ','); // 增長千分位符號
return start + str.substring(index);
};
return str;
}
const pennyMoney = formatMoney(100); // 單位是分
pennyMoney(1000000); // 10,000.00
pennyMoney(123456); // 1,234.56
const dimeMoney = formatMoney(10); // 單位是角
dimeMoney(1000000); // 100,000.00
dimeMoney(123456); // 12,345.60
formatMoney(1)(1000000); // 1,000,000; // 元
複製代碼
咱們經過柯里化的方式預先傳入單位,返回一個針對該單位的格式化方法,這樣咱們就能在不一樣的狀況下加以複用,其中 pennyMoney
與 dimeMoney
也能加以複用,不用在每次都傳入要經過什麼單位來格式化。
高階函數是一個函數,他能夠把其餘函數做爲參數輸入或者做爲其返回值輸出。
原生的方法有不少都是高階函數,例如 Array.prototype.map
方法,他接收一回調函數,從回調函數中獲取返回值,再使用這些值建立一個新的數組並返回。
上述咱們經過柯里化的方式優化了格式金額的方法,能夠看到柯里化的方式也會返回一個函數,那麼咱們能夠認爲,他也是高階函數。下面咱們將再自定義一些高階函數,讓咱們更加理解高階函數的應用。
我有一個方法會根據窗口大小進行動態加載對應的組件,既然須要動態加載組件那麼相對於網絡、內存等消耗是要大一些的,那麼咱們就能夠進行一個防抖操做,讓窗口大小發生變化時不必要那麼實時變化。
const debounce = (func, wait) => {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func(...args);
}, wait)
}
}
window.addEventListener('resize', debounce(() => {
if (window.matchMedia('(min-width:768px)')) {
// loader component
}
console.log('resize');
}, 500))
複製代碼
上述代碼實現了一個 debounce
函數,將接收一個函數和一個等待時間做爲參數,返回一個新的函數,函數執行時會作一個延時防抖的操做。這樣在窗口頻繁的 resize
時短期內也不會屢次觸發加載組件的方法。固然該防抖函數還能應用於其餘不少場景。
const compose = (...funcs) => arg => {
return funcs.reduce((val, f) => f(val), arg);
}
複製代碼
這是一個很經典的高階函數,他接收多個函數做爲參數,返回一個函數,返回的這個函數呢會接收一個參數。那他的做用是什麼呢?能夠執行如下代碼看看。
const compose = (...funcs) => arg => {
return funcs.reduce((val, f) =>{
console.log(val);
return f(val);
}, arg);
}
const pennyTransform = money => money / 100;
const fixedMoney = money => (+money).toFixed(2);
const thousandthMoney = str => {
const index = str.indexOf('.');
if (index > 3) {
const start = str.substring(0, index).replace(/\B(?=(?:\d{3})+$)/g, ',');
return start + str.substring(index);
};
return str;
};
const formatMoney = compose(pennyTransform, fixedMoney, thousandthMoney);
formatMoney(1000000); // 10,000.00
// log =>
// 1000000
// 10000
// 10000.00
複製代碼
上述代碼依舊是格式化金額,只不過換了種寫法。咱們能夠從輸出的 log 中看出,咱們的方法 compose
是吧全部的方法進行一個組合,依次調用,將上一個函數的返回值傳入給下一個參數(第一個參數爲調用時傳的參數)。這樣咱們就能夠將函數功能拆分的很細,一個只作一件事,每一個都是純函數,每個很小的功能就是一個粒子,咱們能夠隨意將其組合拆分,使其應用場景更普遍。
不少同窗或許搞不懂 compose
這個函數具體的邏輯,接下來就仔細講一講。能夠在代碼中看到最重要的 reduce
這個函數,這是數組原生的一個方法,能夠先看看MDN上對這個方法的描述 Array.prototype.reduce:方法會對數組中的每個元素執行傳入的函數,再將其彙總結果返回。
接收兩個參數:
callback
執行數組中每一個值,此函數會接收四個參數,這裏咱們只看用到的前兩個參數:
accumulator
累計器累計回調的返回值,簡單理解就是上一個調用回調函數的返回值。若是是第一個次調用那麼就是下面的 initialValue
或者 undefined
。
currentValue
數組中當前正在處理的元素。咱們數組內的元素都是方法,也就是這裏將是當前須要執行的方法。
initialValue
第一次調用回調函數傳的值。
const compose = (...funcs) => arg => {
return funcs.reduce((val, f) =>{
// 第一次執行 val 爲傳入的 arg
return f(val); // 將 val 傳入 currentValue(當前須要執行的方法),這裏也會將函數調用的結果進行當即返回,返回的值將會在執行到下一個函數時當 val 使用。
}, arg);
}
複製代碼
大體講了講函數的執行流程,方便理解,有問題歡迎評論區留言,有什麼邏輯BUG、字打錯了什麼的也歡迎糾正。
參考
本來打算順帶把高階組件寫一寫,想想仍是放到下一個文章裏面吧,打算寫一寫 React的高階組件與自定義Hook。有興趣的朋友能夠點點關注,說不定我啥時候就更新了,如今這篇文章四月份就開始寫了,斷斷續續一直寫到了七月份,也忙、也懶。
最後再打個廣告,有上海公司要招前端麼,3年工做經驗,主要使用React,Vue、小程序什麼的也都用過、服務端渲染也搞過。要求:不要是外派、不要99六、不要頻繁加班,最好閒一些,工資少點也是闊以滴,這樣就有時間寫寫文章了。個人微信號 jaceyi
。