编码风格
曾经在一篇知名博客看到过这样的一个分享,当我看完后,顿时醍醐灌顶,并在日后的代码中尽量保持这种风格。
类似于语文当中的总分的写作手法,亦或是自顶而下,分而治之,不管怎么说,就是编码的时时候只是考虑同一思维层次的逻辑,在这层完成之后再思考下一层
举个例子,如果我要写一个打车去上班的程序,那么,可以分为以下几步
function goWorkByCar() {
dressUp()
wash()
goDown()
goByCar()
}
那么,以上这些过程就是同一层级的逻辑,顺着这个顺序,我相信很多人都可以把一个复杂的东西分为这几步。 通过函数名,我就可以清晰的知道,这一层级需要做哪些事情,甚至还不用注释。顺着这四个函数,它们的具体实现就是下一层级的东西
function dressUp() {
var clothes = takeClothesFromDrobe()
dress(clothes.up)
dress(clothes.down)
}
这一层级就是 'dressUp' 的这一层级的具体逻辑,先从柜子里拿出衣服,再穿上半身,然后下半身。此时我的注意力已经不再打车上班,打车上班的具体实现我也根本不需要关心。
然后是洗漱
function wash() {
brushTeesh()
washFace()
}
这里的 brushTeesh() washFace() 又属于下一个层级的东西,在这里我们也不需要关心它的实现细节。
同理,下楼,打车都是一样,它们都有各自的逻辑层次。然后你就会发现,其实我们在不停地填函数,每个函数里面又出现新的函数,且每个函数的逻辑都是清晰可以预测的,保持它们只完成一件事情,那么我们只需要关心每个函数的具体操作,例如,我在 dressUp 里根本不关系你怎么洗脸或是下楼。
如果你把一大坨代码都放到打车上班这个函数里,哪实现的具体细节就会分离我们关注的点,也容易出现 bug。
其实这么编码的好处就是写出来的代码是逻辑清晰的,每一层的东西其实相对简单,我们像搭积木一样不知不觉就把代码写完了。日后编码的时候我们可以看看它的函数名,如果你写的代码和函数名代表的任务不是同一层次的东西,那就是时候把它抽出去单独做一层了。
函数式编程
JS 是一门动态的语言,由于天然的动态属性,可以产生很多高级有趣的用法,其中,函数是最能体现它的风格和其灵活性。 相对于面向对象编程(Object-oriented programming)关注的是数据而言,函数式编程关注的则是动作,其是一种过程抽象的思维,就是对当前的动作去进行抽象。
less time less bug
- 高阶函数
函数是一个值
var calculate = function(a, b, type) {
if (type === 'add') {
return a + b
} else if (type === 'minus') {
return a - b
} else if () {
//
do more ...
}
}
console.log(calculate(3, 2, 'add'), calculate(3,2, 'minus'))
这段程序呢, 可以实现功能,但是一大坨条件判断堆在里面,如果 type 类型很多,并且每一个类型中具体的过程又很复杂,就很不好维护
每一种类型其实都可以抽象成函数
function add(num1, num2) {
return num1 + num2
}
function minus(num1, num2) {
return num1 - num2
}
function doAnyThing(num1, num2) {
return `i could do any ${num1}${num2}`
}
var calculateCB = function(a, b, cb) {
return cb(a, b)
}
console.log(calculateCB(3, 2, add), calculateCB(3,2,minus), calculateCB(3,2, doAnyThing))
这样,即使函数参数的类型不确定,我也丝毫不慌张,你要添加什么类型,无非就是我写个相应的函数,而且具体函数里面做了什么,我也并不关心
- 纯函数
函数式编程关注的是行为,是对一层行为的抽象。一般在编码的过程中,我们会有意的将一些列公用或者相同功能的代码封装成函数,以便在不同的地方调用。
纯函数有两个特点
· 同输入同输出
· 无副作用 不会影响外部或全局变量,如发送请求,修改 DOM
// 是纯函数
function add(x,y){
return x + y
}
// 输出不确定,不是纯函数
function random(x){
return Math.random() * x
}
// 有副作用,不是纯函数
function setColor(el,color){
el.style.color = color ;
}
// 输出不确定、有副作用,不是纯函数
var count = 0;
function addCount(x){
count+=x;
return count;
}
- 函数合成与柯里化
UI => VIEW => (state, props)
y = f(x) => gY = g(f(x))
那么,什么是函数合成呢? 如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)
function addAndMultiply(x) {
return (x + 6) * 6
}
function add(x) {
return x + 6
}
function multiply(x) {
return x * 6
}
multiply(add(x))
到这里或许你已经有点眉目了,函数合成即接收函数为参数,返回一个函数compose,根据定义,可以写出如下代码
function compose(foo, bar) {
return function(x) {
return foo(bar(x))
}
}
var calculate = compose(multiply, add)
calculate(1)
function compose() {
var args = arguments;
var start = args.length - 1;
return function () {
var i = start - 1;
var result = args[start].apply(this, arguments);
while (i >= 0){
result = args[i].call(this, result);
i--;
}
return result;
};
}
- 函数柯里化
柯里化,又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
function add(num1, num2) {
return num1 + num2
}
add(1,2)
const addCurry = num1 => num2 => num1 + num2
addCurry(1)(2)
// 参数只能从左到右传递
function createCurry(func, arrArgs) {
var args=arguments;
var funcLength = func.length;
var arrArgs = arrArgs || [];
return function(param) {
var allArrArgs=arrArgs.concat([param])
// 如果参数个数小于最初的func.length,则递归调用,继续收集参数
if (allArrArgs.length < funcLength) {
return args.callee.call(this, func, allArrArgs);
}
// 参数收集完毕,则执行func
return func.apply(this, allArrArgs);
}
}
var curry = createCurry(function(a, b, c) {
return a + b + c
})
curry(1)(2)(3)
一些著名的库的核心代码都实现了curry和compose的过程,如lodash, redux
总结: 函数式编程显然更加考验从抽象的逻辑能力,特别是柯里化和函数合成是是有点饶的,但正因为如此,才有必要学习和理解这一思想,倘若在平时的开发过程中,能将函数式编程的思想运用其中,也是一大闪光点。