函数式编程
壹. 函数式编程的出现
- 发展历程:
- 命令(脚本)式
- 面向对象式
- 函数式编程
问题
例:参数 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" },
// ];
const _array = [
"progressive$%coding",
"objective$%coding",
"functional$%coding",
];
const _objArr = [];
const nameParser = (arr, obj) => {
arr.forEach((item) => {
let names = item.split("$%");
let newName = [];
names.forEach((name) => {
let nameItem = name[0].toUpperCase() + name.slice(1);
newName.push(nameItem);
});
obj.push({ name: newName.join(" ") });
});
return obj;
};
// 实现方式存在问题:
// 1. 过程中存在大量包裹逻辑 - 看完整段代码菜才能明白在做什么 (不透明,不解耦)
// 2. 存在跨包裹的临时变量,首尾封闭 - 迭代拓展的难度高
解决方案
// 1. 需求分子:数组转为数组对象 => [字符串 > 对象]
// nameParser => [objHelper :: string > object]
// 2. 功能明确:objHelper => formatName + assembleObj
// 3. 功能拆分:objHelper => [(split + capitalize)] + assembleObj
// 代码实现:
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);
// 申明结构
const formatName = 组装合并(join(" "), map(capitalize), split("$%"));
const objHelper = 组装合并(assembleObj("name"), formatName);
const nameParser = map(objHelper);
nameParser(_array);
贰. 函数式编程原理特点
1. 什么是函数式编程
- 原子组合的变化
- 拆分、组合
2. 定义
a. 什么是函数:逻辑功能的实现落脚点,实现函数 + 拼接流程 b. 声明式编程:声明需求 - 更贴近语言习惯 c. 惰性执行
// 惰性函数
const program = (name) => {
if (name === "progressive") {
return (program = () => {
console.info("this is progressive");
});
} else if (name === "objective") {
return (program = () => {
console.info("this is objective");
});
} else {
return (program = () => {
console.info("this is functional");
});
}
};
program("progressive")();
d. 无状态,无副作用
- 无状态:幂等;数据不可变:不可以操作改变数据源
- 无副作用:函数内部不应直接对整个系统中任何参数变量做改动
叁. 实际开发
1. 纯函数的改造
const _class = {
name: "objective",
age: 1,
};
const score = (str) => (_class.name = str); // 副作用
const changeScore = (_class) => _class.age++; // 改变状态
// ####
const score = (obj, str) => obj.name + str;
const changeScore = (obj, str) => {
let score = obj.age;
return score++;
};
2. 流水线组装、加工
a. 加工 - 函数柯里化
// f(x, y, z) => f(x)(y)(z)
const sum = (x, y) => x + y;
sum(1, 2);
const add = (x) => {
return (y) => {
return x + y;
};
};
const increment = add(1);
increment(2);
// const fetch = ajax(method, url, params);
// const fetch = ajax.get();
// const request = fetch(url);
// 组合(fetch, request);
// 要实现 体系:加工 + 组装。单个模块加工,输入输出应该单值化 => 单元函数 (柯里化)
add(1)(2)(3) 拆分传参 :
// 1. 构造柯里化结构 => 函数封装
// 2. 输入:处理外层的 arguments => 类数组处理
// 3. 传入的参数形式无限拓展 => 递归 返回递归函数本身
// 4. 主功能区:累加
// 5. 输出:函数 vs 产出 toString 隐式转换
// const add = () => {
function add() {
let args = Array.prototype.slice.call(arguments);
// let args = arguments.toArray();
// 内层 - 递归 + 参数收集
let inner = () => {
args.push(...arguments);
return inner;
};
// 外层 - 主功能区
inner.toString = () => {
return args.reduce((prev, cur) => {
return prev + cur;
});
};
// 返回
return inner;
}
// " " + add(1)(2)(3)
b. 流水线 - 组装函数
const compose = (f, g) => (x) => f(g(x));
const sum1 = (x) => x + 1;
const sum2 = (x) => x + 2;
const sum12 = compose(sum1, sum2);
// 实际实现
// 命令式
reverse(toUpperCase(trim(map(arr))));
// 面向对象
arrObjInstance.map().trim().toUpperCase().reverse();
// 函数式
const result = compose(reverse, toUppercase, trim, map);
// compose -> 借助第三方库实现,比如:Ramda.js
肆. BOX 与 函子(Functor)
Functor函子: Functor 是实现了 map 函数并遵守一些特定规则的容器类型。- 特定规则
- 只有一个属性对象(它可以是任意类型的值)
- 实现 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((mail) => {
return read(mail);
});
// 3. 处理这封信
let mail3 = mail1.map((mail) => {
return burn(mail);
});
// 4. 其他人查看
mail3.map((mail) => {
return mail;
});
// 链式调用
new Mail("love").map(write).map(read).map(burn).map(check);