本系列文章将经过本身的一个开发工具库的实战经验(踩过的坑)教你们如何开发属于本身的一个工具库,在这里你能够学到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随机数
复制代码
本章节讲了函数式编程的一些主要概念,以及为什么用它来开发一个工具库是很好的,由于纯函数都是“干净”的,不依赖外部也不会对外部有影响,不用担忧会影响到原有的代码。
下章节咱们来说下如何为本身的项目编写测试用例。