本系列文章將經過本身的一個開發工具庫的實戰經驗(踩過的坑)教你們如何開發屬於本身的一個工具庫,在這裏你能夠學到Git的使用規範,基礎項目的搭建,代碼編寫的規範,函數式編程思想,TypeScript實戰,單元測試,編寫文檔和發佈NPM包等等知識。git
閱讀文章你可能須要如下基礎知識:es6
Windlike-Utilsgithub
由於函數式編程不會改變外部的變量,且對固定輸入有惟一輸出,這樣咱們能夠無論函數內部的具體實現去使用它,並且能夠很方便地經過組合多個函數而成咱們想要的那個函數,更接近天然語言的表達。編程
好比咱們要實現一個y=f(x)=2*x+1
的函數,一般咱們會這麼寫:數組
function f(x) {
return 2*x + 1;
}
f(1); // 3
複製代碼
而函數式編程則是將他們拆分爲幾個小函數,再組裝起來使用:緩存
function double(x) {
return 2*x;
}
function plusOne(x) {
return x + 1;
}
plusOne(double(1)); // 3
// 或者還有更好一點的寫法,這裏暫未實現,
// 這裏只是寫下他們的調用方法,具體下面的文會講到
const doubleThenPlusOne = compose(plusOne, double);
doubleThenPlusOne(1);
複製代碼
y=f(x)
,當輸入的x
不變,輸出的y
也不會改變這是一個栗子:閉包
const array = [1, 9, 9, 6];
// slice是純函數,由於它不會改變原數組,且對固定的輸入有惟一的輸出
array.slice(1, 2); // [9, 9]
array.slice(1, 2); // [9, 9]
// splice不是純函數,它即改變原數組,且對固定輸入,輸出的結果也不一樣
array.splice(0, 1); // [9 ,9 ,6]
array.splice(0, 1); // [9 ,6]
複製代碼
柯里化就是傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數。 咱們上面實現了一個加一的函數,但當咱們又須要一個加二的函數,又從新去寫代碼實現它的話效率是很低的,因此咱們就須要柯里化,咱們設想一下可不能夠這樣呢:app
const plusOne = add(1);
const plusTwo = add(2);
plusOne(1); // 2
plusTwo(2); // 4
複製代碼
這樣咱們就能夠很容易地獲得想要的函數,下面是add
函數的實現:dom
function add(a) {
return function(b) {
return a + b;
}
}
複製代碼
雖然基本知足咱們如今的需求,但感受仍是不太方便,若是咱們要實現三個或多個數的相加咱們可能得這樣寫:函數式編程
function add(a) {
return function(b) {
return function(c) {
return a + b + c;
}
}
}
複製代碼
因而咱們再設想一種更方便的方法:
function add(a, b, c) {
return a + b + c;
}
const curryAdd = curry(add);
const plusOne = curryAdd(1);
const plusOneAndTwo = curryAdd(1, 2);
plusOne(2, 3); // 6
plusOneAndTwo(3); // 6
curryAdd(1)(2, 3); // 6
curryAdd(1)(2)(3); // 6
複製代碼
這樣咱們就能夠自由產生須要參數不一樣的函數啦,下面是curry
的實現方法(有興趣的同窗能夠先思考下再看):
function curry<Return>(fn: Function): CurryFunction<Return> {
// 記錄傳進來的函數總共須要多少個參數
let paramsLength: number = fn.length;
function closure(params: any[]): CurryFunction<Return> {
let wrapper: CurryFunction<Return> = function (...newParams: any[]) {
// 將全部的參數取出
let allParams = [...params, ...newParams];
if (allParams.length < paramsLength) {
// 若是參數數量還不夠則返回新的函數
return closure(allParams);
} else {
// 不然返回結果
return fn.apply(null, allParams);
}
};
return wrapper;
}
return closure([]);
}
複製代碼
可能有些不太好理解,一時看不懂的同窗能夠先跳過這裏看下面~
另外也能夠用原生的bind
函數來實現柯里化:
const plusOne = add.bind(null, 1);
plusOne(2, 3);
複製代碼
函數組合就是把多個不一樣的函數組合成一個新的函數。
好比這樣:
// 將函數從右往左組合
const doubleThenPlusOne = compose(plusOne, double);
// 1*2 + 1
doubleThenPlusOne(1); // 3
複製代碼
function compose<Return>(...fn: any[]): (...params: any[]) => Return {
return (...params: any[]): Return => {
let i = fn.length - 1;
let result = fn[i].apply(null, params);
while (--i >= 0) {
result = fn[i](result);
}
return result;
};
}
複製代碼
有時候這個世界並非那麼美好的,並非全部的代碼都是那麼「乾淨」的,好比I/O操做和DOM操做這些等待,由於這些操做都對外部有依賴,會對外部有影響。這時候就須要用延遲輸出來保證咱們的函數是「乾淨」的,例以下面實現的這個random
函數:
function random(min: number = 0, max: number, float: boolean): () => number {
return (): number => {
if (min > max) {
[min, max] = [max, min];
}
if (float || min % 1 || max % 1) {
return min + Math.random() * (max - min);
}
return min + Math.floor(Math.random() * (max - min + 1));
};
}
複製代碼
對於固定的輸入,它總返回的是產生符合條件的隨機數的函數,這樣咱們就經過「拖延症」來讓咱們的代碼保持「乾淨」啦,是否是很機智呢!這樣作的好處還有它經過閉包機制把參數都記住,緩存起來,下次能夠不用重複傳一樣的參數:
const createRandomNumber = random(1, 100, false);
createRandomNumber();
createRandomNumber(); // 能夠屢次重複調用產生1到100隨機數
複製代碼
本章節講了函數式編程的一些主要概念,以及爲什麼用它來開發一個工具庫是很好的,由於純函數都是「乾淨」的,不依賴外部也不會對外部有影響,不用擔憂會影響到原有的代碼。
下章節咱們來說下如何爲本身的項目編寫測試用例。