|
一、函数式编程的出现
- 命令(脚本)式编程 -> 以一行代码为单元,逐步运行
- 面向对象编程 -> 以对象为操作单位
- 函数式编程 -> 函数为王
1. 问题的出现 - 从一道面试题说起
// 面试题: 上接浏览器原理 —— 参数parse
// 1. url中参数是如何展示数组的
// location.search => '?name[]=progressive$%coding&name[]=objective$%coding&name[]=functional$%coding'
// 2. 参数提取拼接数组
// ['progressive$%coding', 'objective$%coding', 'functional$%coding']
// 3. 转换成数组对象做存储
// [{name: 'Progressive Coding'}, {name: 'Objective Coding'}, {name: 'Functional Coding'}]
// 解:
// 1. 字符串拆分数组 遍历
// 2. 字符串=>key value 遍历
const _array = ['progressive$%coding', 'objective$%coding', 'functional$%coding'];
const _objArr = [];
const nameParser = (array, objArr) => {
// 对于数组的处理部分
array.forEach(item => {
let names = item.split('$%');
let newName = [];
// 对于name的处理部分
names.forEach(name => {
let nameItem = name[0].toUpperCase() + name[1].slice(1);
newName.push(nameItem);
})
objArr.push({
name: newName.join(' ');
})
})
return objArr;
}
// 问题:
// 1. 过程中存在逻辑包裹 - 看完整段才明白在做啥
// 2. 存在临时变量,首尾封闭 - 迭代拓展难度较高
解决方案
// step1. 需求分析 => 数组 > 数组对象 => [字符串 > 对象]
// nameParser => [objHelper :: string > object]
// step2. 功能明确 => objHelper = formatName + assembleObj
// step3. 功能拆分 => objHelper = [(split + capitalize + join)] + assembleObj
// step4. 代码实现
const _array = ['progressive$%coding', 'objective$%coding', 'functional$%coding'];
// 原子操作
const assembleObj = (key, x) => {
let obj = {};
obj[key] = x;
return obj;
}
const capitalize = str => str[0].toUpperCase() + str.slice(1);
// 声明结构 - ramda
const formatName = R.merge(join(' '), map(capitalize), split('$%'));
const objHelper = R.merge(assembleObj('name'), formatName);
const nameParser = map(objHelper);
nameParser(_array);
// 面试题:正确的使用遍历 - for forEach map filter sort……
// 本质作用 => 通用遍历 | 遍历逻辑处理 | 生成数组 - 处理 | 生成数组 - 过滤 | sort - 排序……
二、函数式编程原理特点
1. 什么是函数式的原理
- 加法结合律 | 因式分解 | 完全平方公式 => 原子组合的变化 a + b + c = (a + b) + c
- 水源 => 组合(水管 + 弯头) =>花洒
2. 理论思想
a. 一等公民 —— 函数 => 1. 逻辑功能的落脚点 —— 函数 2. 实现函数 + 拼接流程
b. 声明式编程 => 声明需求 - 更贴近语言习惯 react vue3
c. 惰性执行 - 无缝衔接,性能节约
// 惰性函数
const program = name => {
if (name === 'progressive') {
return program = () => {
console.log('this is progressive');
}
} else if(name === 'objective') {
return program = () => {
console.log('this is objective');
}
} else {
return program = () => {
console.log('this is functional');
}
}
}
program('progressive')();
program();
3. 无状态与无副作用
- a. 无状态 - 幂等;数据不可变 - 不可操作改变源数据
- b. 无副作用 - 函数内部不可直接对系统中任何参数变量做直接改动
三、实际开发
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 'lodash';
const _class = {
name: 'objective'
}
const score = (obj, str) => _.deepClone(obj).name + ':' + str;
const changeClass = (obj, name) => ({ ...obj, name });
changeClass(_class, 'functional');
score(_class, 'good');
2. 流水线的组装 - 加工 & 组装
a. 加工 - 科里化
// f(x, y, z) -> f(x)(y)(z)
const sum = (x, y) => {
return x + y;
}
sum(1, 1);
const add = x => {
return y => {
return x + y;
}
}
add(1)(1);
// 要实现 体系 = 加工 + 组装,单个加工的输入输出应该单值化 -> 单元函数
const fetch = ajax(method, url, params);
const fetch = ajax.get(url);
const request = fetch(params);
const fetch = ajax(method)(url)(params);
组合(request, fetch)
- 面试题:手写构造可拆分传参的累加函数 add(1)(2)(3)(4)
// 1. 构造科里化的结构
// 2. 输入 处理外层arguments => 类数组处理
// 3. 传入参数无线拓展 => 内层递归 => 返回递归函数本身
// 4. 主功能区
// 5. 输出 从函数到产出 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)(4) // 10
// 如何去调原来的toString => 用.call
b. 流水线 - 组装函数
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)
pipe() // history | grep rm
四、函子
// 一封情书
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 cross(mail);
});
// 4. 别人查看的时候
mail3.map(function(mail) {
return read(mail);
})
// 链式
new Mail('love').map(read).map(burn).map(read);
// Functor(函子)遵守了一些特定规则的容器或者数据协议
// 具有一个通用的map方法,返回新实例
// 具有结合外部的运算能力 => 可在管道中处理不同层级又很纯净的单元操作 |
|