一、函数式编程的出现
发展历程:
- 命令(脚本)式编程 → 以一行代码为单元,逐行运行
- 面向对象编程 → 以对象为操作单位
- 函数式编程 → 以行数为操作单位
从面试题开始
-
url中是如何展示数组的
location.search => '?name[]=progressive$%coding&name[]=objective$%coding'
-
参数提取,拼接数组
['progressive$%coding', 'objective$%coding']
-
转换成数组对象存储
[{name: 'Progressive Coding'}, {name: 'Objective Coding'}]
解:
- 字符串拆分数组
- 字符串 → key value 遍历
const _array = ['progressive$%coding', 'objective$%coding'];
const _objArr = [];
const nameParser = (array, objArr) => {
array.forEach(item => {
// 将参数拆分成两部分
let names = item.split('$%');
let newName = [];
// 遍历处理两部分的参数
names.forEach(name => {
let nameItem = name[0].toUpperCase() + name[1].splice(1);
newName.push(nameItem);
})
objArr.push({
name: newName.join(' ');
})
})
return objArr;
}
问题:
- 过程存在逻辑包裹,需要看完整段代码才清楚代码在做什么,依赖注释
- 存在临时变量,首尾封闭,难以扩展
解决方案
-
需求分析: 数组 → 数组对象 ⇒ [字符串 → 对象]
nameParser ⇒ [objHelper :: string → object]
-
功能明确:
objHelper = formatName + assembleObj
-
拆分功能:
objHelper = [(split + capitalize + join)] + assembleObj
-
代码实现
const _array = ['progressive$%coding', 'objective$%coding']; // 原子操作 const assembleObj = (key x)=>{ let obj = {}; obj[key] = x; return obj; } const capitalize = str=>str[0].toUpperCase() + str.slice(1); // 声明结构 // 组装合并(join(' '), map(capitalize), split('$%')) const formatName = // 组装合并(assembleObj('name'), formatName() const objHelper = const nameParser = map(objHelper); nameParser(_array);
面试题:如何正确使用遍历
- for:通用
- forEach:遍历逻辑处理(每一项都处理)
- map:生成新数组(数组生成器,恰好可以遍历数组)
- filter:生成新数组,过滤数组中某一些项
- sort:排序
- ……
二、函数式编程原理特点
1. 什么是函数式的原理
-
原子组合变化 ⇒ 功能输入、输出保持不变,可以随意改变顺序
eg:小鳄鱼洗澡:水源 ⇒ 组合(水管、弯头) ⇒ 花洒
2. 理论思想
-
一等公民:函数
- 逻辑功能的落脚点—函数
- 实现函数 + 拼接流程
-
声明式编程:声明需求
- 更贴近语言习惯
-
惰性执行
- 无缝衔接
- 性能节约
// 惰性函数 const program = name => { if (name === 'progressive') { return program = () => { console.log('this is progressive'); } } if (name === 'objective') { return program = () => { console.log('this is objective'); } } return program = () => { console.log('this is function'); } } program('progressive'); program()
3. 无状态与无副作用
- 无状态:数据不可变,不可操作改变源数据
- 无副作用:函数内部不可直接对系统中任何参数变量改动
三、实际开发
1. 纯函数的改造
const _class = {
name: "objective"
}
// 函数内部引用了外部变量,违反无状态
const score = str => _class.name + ':' + str;
// 修改传入的参数,违反无副作用
const changeClass = (obj, name) => obj.name = name;
changeClass(_class, 'functional');
score('good');
import _ from 'loadash'
const _class2 = {
name: 'objective'
}
const score2 = (obj, str) => _.deepClone(obj).name + ':' + str;
const changeClass2 = (obj, name) => ({
...obj,
name
});
changeClass2(_class, 'functional');
score(_class, 'good');
2. 流水线的组装
-
加工:科里化
意义:实现单个加工的输入输出单值化 ⇒ 单元函数
-
面试题:手写构造可拆分传参的累加函数
- 构造科里化的结构
- 输入 处理外层的arguments ⇒ 类数组处理
- 传入参数无限扩展 ⇒ 内层递归 ⇒ 返回递归函数本身
- 主功能区
- 输出 从函数到产出 toString的替换
const add = function () { // input let args = Array.prototype.slice.call(arguments); // 内层函数构建 let inner = function () { args.push(...arguments); return inner; } // 主功能区 inner.toString = function () { return args.reduce((prev, cur) => { return prev + cur; }) } return inner; } add(1)(2)(3).toString(); // 如何调原来的toString => .call
-
-
流水线—组装函数
const compose(f, g) => x => f(g(x)); const sum1 = x => x + 1; const sum2 = x => x + 2; const sum12 = compose(sum2, sum1); sum12(1);
-
实际实现使用
// 命令式 trim(reverse(toUpperCase(map(arr)))); // 面向对象 arr.map().toUpperCase().reverse().trim() // 函数式 const result = compose(trim, reverse, toUpperCase, map)
四、函子
// 一封情书
class Mail {
constructor(content) {
this.content = content;
}
map(fn) {
return new Mail(fn(this.content));
}
}
// 1. 小李写情书
let mail1 = new Mail('love');
// 2. 小张读信
let mail2 = mail1.map(function (mail) {
return read(mail);
})
// 3. 小张担心泄漏信息,涂抹了信件内容
let mail3 = mail2.map(function (mail) {
return burn(mail);
})
// 4. 八卦的人查看
mail3.map(function (mail) {
return check(mail);
})
new Mail('love').map(read).map(burn).map(read);
Functor
函子遵守了一些特定规则的容器或数据协议
具有一个通用的map方法,返回新实例
具有结合外部的运算能力 ⇒ 可在管道中处理不同层级又很纯净的单元操作