三种编程的范式

  1. 命令式,C 语言

  2. 面向对象,Java、C++

  3. 函数式,Haskell、Clojure

那么,JavaScript是哪种?

三种都是,JavaScript同时包罗了这 3 种编程范式,JavaScript果然是非常自由的语言。

而浏览器使用JavaScript语言的发展也是这个顺序:

  1. 我们写原生JS或者使用JQueary的时候我们是命令式

  2. 后俩开始使用vue2与react16.8之前(class语法)的时候框架是面向对象的思路

  3. 再到vue3与react hooks语法阶段,函数式编程就开始占据上风,占领统治地位了。

所以兄弟们,让我们开始拥抱函数式编程吧!

举个栗子

['john-reese', 'harold-finch', 'sameen-shaw'] 
// 转换成 
[{name: 'John Reese'}, {name: 'Harold Finch'}, {name: 'Sameen Shaw'}]

1. 命令式编程风格

let names = ['john-reese', 'harold-finch', 'sameen-shaw'];
let result = [];

for (let i = 0; i < names.length; i++) {
    let parts = names[i].split('-');
    let formattedName = parts.map(part => part.charAt(0).toUpperCase() + part.slice(1)).join(' ');
    result.push({ name: formattedName });
}

console.log(result);

2. 面向对象风格

class NameFormatter {
    constructor(names) {
        this.names = names;
    }

    formatNames() {
        return this.names.map(name => {
            let parts = name.split('-');
            let formattedName = parts.map(part => part.charAt(0).toUpperCase() + part.slice(1)).join(' ');
            return { name: formattedName };
        });
    }
}

let formatter = new NameFormatter(['john-reese', 'harold-finch', 'sameen-shaw']);
let result = formatter.formatNames();
console.log(result);

3. 函数式风格

  const names = ['john-reese', 'harold-finch', 'sameen-shaw']
  const capitalize = x => x[0].toUpperCase() + x.slice(1).toLowerCase();
  const genObj = (key) => {
    return { name: key };
  }
  let result = names.join(' ').split('-').map(capitalize).map(x => genObj(x))
  console.log(result);

大家是不是明显感觉代码清晰,且直观,方便阅读了。重点是函数,通过函数的组合去解决问题。

函数式编程的特点

  1. 函数是一等公民,我们基本的操作都是在操作函数。

  2. 全程声明式,React是声明式,我们只需要描述UI,以及控制更新,其余交给React;或者像不像SQL语句,我们不知道SQL语句是如何实现的,但是我们知道怎么用。

  3. 惰性执行,函数不会提前执行,而是等到真正需要的时候才执行

  4. 无状态,一个函数在任何时候输入相同,输出必须相同(带有网络请求或者获取Date的都不行)

  5. 其他数据不可变/没有副作用,如果你想修改一个对象,那你应该创建一个新的对象用来修改,而不是修改已有的对象。

副作用:一开始看到这个词大家可能觉得这肯定是一个不好的东西,毕竟副作用在我们印象中的感觉是做了多余的事情。而实际上的含义是,在完成函数主要功能之外完成的其他副要功能。而这个副要功能有时候却是必要功能。

函数式编程详解

示例代码

// curry 函数借助 Function.length 读取函数元数
function curry(func, arity=func.length) {
  // 定义一个递归式 generateCurried
  function generateCurried(prevArgs) {
    // generateCurried 函数必定返回一层嵌套
    return function curried(nextArg) {
      // 统计目前“已记忆”+“未记忆”的参数
      const args = [...prevArgs, nextArg]  
      // 若 “已记忆”+“未记忆”的参数数量 >= 回调函数元数,则认为已经记忆了所有的参数
      if(args.length >= arity) {
        // 触碰递归边界,传入所有参数,调用回调函数
        return func(...args)
      } else {
        // 未触碰递归边界,则递归调用 generateCurried 自身,创造新一层的嵌套
        return generateCurried(args)
      }
    }
  }
  // 调用 generateCurried,起始传参为空数组,表示“目前还没有记住任何参数”
  return generateCurried([])
}
// 使用展开符来获取数组格式的 pipe 参数
const compose = (...fns) => {
  const fnReversed = fns;
  return args => {
    return fnReversed.reduceRight((ret, fn) => fn(ret), args);
  };
};
const pipe= (...fns) => {
  const fnReversed = fns;
  return args => {
    return fnReversed.reduce((ret, fn) => fn(ret), args);
  };
};
// 柯里化
// f(a,b,c) → f(a)(b)(c) / f(a)(b,c) / f(a,b)(c)
function add(a, b, c) {
  return a + b + c;
}
const addCurry = curry(add);
addCurry(1)(2)(3);// 6
addCurry(1,2)(3);// 6
addCurry(1)(2,3);// 6
// 函数组合
const f = x => x + 1;
const g = x => x * 2;
const fg = compose(f, g);
fg(1) //3

代码挺抽象,大家没看明白没关系,我给大家讲讲

柯里化

柯里化,就是把一个多参数的函数,转化为单参数函数,也叫单元函数

函数组合,就是把多个函数合并起来组合成一个新的函数。

对比代码

// 命令式的写法
log(toUpperCase(head(reverse(arr))))
// 面向对象的写法
arr.reverse()
  .head()
  .toUpperCase()
  .log()
// 函数组合
compose(log, toUpperCase, head, reverse);
pipe(reverse, head, toUppderCase, log);

函数组合让代码变得简单并且增加了可读性

总结

函数式编程的优点:代码简洁,方便理解,减少BUG

缺点:性能肯定低于命令式辣。

推荐库:Lodash-fpramda(推荐)

未曾清贫难成人,不经打击老天真。 自古英雄出炼狱,从来富贵入凡尘。 醉生梦死谁成气,拓马长枪定乾坤。 挥军千里山河在,立名扬威传后人。