• 作者:老汪软件技巧
  • 发表时间:2024-11-13 00:02
  • 浏览量:

但行好事 莫问前程

前言

在程序开发中,我们需要遵守各种繁杂且关键的设计原则:可维护性、扩展性、复用性、模块化 ...

但在实际开发中,代码不可避免的会变得复杂、笨重和难以维护。而这可能与过度依赖 命令式编程(Imperative Programming) 有关。

命令式编程 —— 具有 直观性、控制性和高性能 等优点,也存在 维护性差、灵活性低、难以复用 等缺陷

这并不是在反对命令式编程,而是在合适的场景下,我们有更好的选择

此时同为编程思想基石之一的 函数式编程(Functional Programming, FP),便是解决问题的良药。

本文我们一起学习 函数式编程是什么,如何使用,以及它的优缺点。

案例

进入学习之前先看一个案例:

在一系列人名数组中,我们需要对人名做一些转换,并修改数据结构方便后续扩展:

['john-reeSe', 'Harold-FINCH', 'kobe-Bryan', 'jane-Eyre'] 
// 转换为 
[{name: 'John Reese'}, {name: 'Harold Finch'}, {name: 'Kobe Bryan'},  {name: 'Jane Eyre'}]

命令式编程:

const arr = ['john-reeSe', 'Harold-FINCH', 'kobe-Bryan', 'jane-Eyre'];
function formatNames(arr) {
    const newArr = [];
    for (let i = 0; i < arr.length; i++) {
      let name = arr[i];
      let names = name.split('-');
      let newName = [];
      for (let j = 0, naemLen = names.length; j < naemLen; j++) {
        let nameItem = names[j][0].toUpperCase() + names[j].slice(1).toLowerCase();
        newName.push(nameItem);
      }
      newArr.push({ name : newName.join(' ') });
    }
    return newArr;
}
formatNames(arr);

这当然能完成任务,但是期间定义了很多临时变量,过程中掺杂了大量逻辑,代码互相纠缠和依赖,可读性较差,一旦出问题很难定位。

而函数式编程怎么做?

const capitalize = x => x[0].toUpperCase() + x.slice(1).toLowerCase();
const genObj = curry((key, x) => new Object({[key]: x})) 
const capitalizeName = compose(join(' '), map(capitalize), split('-'));
const convert2Obj = compose(genObj('name'), capitalizeName)
const convertName = map(convert2Obj);
convertName(['john-reese', 'harold-finch', 'sameen-shaw'])

忽略 compose 和 curry,上述的代码,使用函数组合代替命令组合。

将原本的一大段代码拆为了多个小函数的组合(capitalize、capitalizeName、convert2Obj),灵活、复用性强、可读性高。

上述案例可以看出:

函数式编程的重点是函数而不是过程强调如何通过函数的组合变换去解决问题简介

函数式编程是一种编程范式,主要思想是把运算过程尽量写成一系列嵌套的函数调用。

函数式编程的目的是提高代码的简洁性、可维护性、复用性、减少副作用...

充分理解函数式编程,需要掌握以下概念:

声明式编程风格纯函数数据不可变 & 函数无状态概念声明式编程

函数式编程采用声明式编程的风格,关注于表达逻辑而非控制流,使代码更加简洁、易读、扩展性强、维护性高。

// 命令式
const array = [0, 1, 2, 3];
for(let i = 0; i < array.length; i++) {
   console.log(i);
}
// 声明式
array.forEach(i => console.log(i));

声明式编程关注于 如何用各种表达式来描述程序逻辑,而不关心它们具体是如何实现的。

SELECT name FROM employees WHERE department = 'Sales';

SQL 语句就是声明式的,你无需关心 Select 语句是如何实现的,不同的数据库会去实现它自己的方法并且优化。

纯函数

函数式编程强调使用纯函数进行编程,意义在于使代码 可预测性强、易于测试和调试、减少副作用。

纯函数:无副作用的函数,且相同的输入有相同的输出。

// 非纯函数
let counter = 0;
function increment() {
    return ++counter;
}
// 纯函数
const increment = (counter) => counter + 1

实现纯函数的核心在于:

进阶

为了在开发中更好的实现函数式编程,有两个操作必不可少: 柯里化(Currying) 和 函数组合(Compose)。

柯里化 curry

function add(x, y) {
    return x + y;
}
add(1, 2);  // 3
 
// curry  
function add(x) {
   return function (y) { 
    return x + y;
   }; 
}
const increment =  add(1) 
increment(2);  // 3

柯里化强调的是生成单元函数,它将一个多元函数,转换成一个依次调用的单元函数 f(a,b,c) → f(a)(b)(c)

const replace = curry((a, b, str) => str.replace(a, b));
const replaceSpaceWith = replace(/\s*/);
const replaceSpaceWithComma = replaceSpaceWith(',');
const replaceSpaceWithDash = replaceSpaceWith('-');

通过柯里化拆解繁多的业务逻辑,使得复杂的逻辑可以被分解为简单的、可组合的函数,便于调试和维护。

函数组合 compose

实现一个需求:将数组最后一个元素大写,假设 log、head、reverse、toUpperCase 函数存在

// 命令式写法
log(toUpperCase(head(reverse(arr))));
// 函数组合-方式 1
const last = compose(head, reverse);
const shout = compose(log, toUpperCase);
const shoutLast = compose(shout, last);
shoutLast(arr);
// 函数组合-方式 2
const lastUppder = compose(toUpperCase, head, reverse);
const logLastUpper = compose(log, lastUppder);
logLastUpper(arr);

每个函数就像一根管道,函数组合就是把多根管道连接起来,让数据穿过多个管道,形成最终结果。

组合函数允许将多个小函数组合成一个新函数,它让代码变得简单而富有可读性,同时通过不同的组合方式,我们可以轻易组合出其他常用函数。

总结

‌函数式编程‌(Functional Programming)是一种编程范式,强调使用纯函数来构建程序,注重描述程序逻辑而不是关心具体实现,通过函数的组合变换去解决问题。

优点:

缺点:

落实到前端开发中,合适条件下,使用纯函数减少副作用的影响,使用柯里化拆分出简单、灵活的函数增加复用性,使用函数组合让代码变得简洁且更有维护性。

函数式编程.png

学习函数式编程的意义在于,让你意识到命令式编程和面向对象编程外还有一种全新的编程思想。

软件领域没有真正的银弹,合适的场景选择合适的思路,而不是 “手中只有一个锤子,看什么都像钉子”。

结语

本文写的非常浅显,最近忙着学习、健身、找工作 创作精力不够投入,但还是保持习惯写了一篇,希望简单总结之余能抛砖引玉引起你的兴趣。

希望本文能对你有所帮助。持续更新前端知识,脚踏实地不水文,真的不关注一下吗~

写作不易,如果有收获希望 点赞、收藏

才疏学浅,如有问题或建议还望指教~

参考

《JavaScript ES6函数式编程入门经典》

简明 JavaScript 函数式编程——入门篇


上一条查看详情 +ajax 发送请求时遇到的坑
下一条 查看详情 +没有了