- 发展历程:命令式 => 面向对象 => 函数式
1.由问题引发的思考
想把数组内的元素转换格式:'progressive$%coding' => { name: 'Progressive Coding' }
const _array = ['progressive$%coding', 'objective$%coding', 'functional$%coding']
const _objArr = [];
//[{ name: 'Progressive Coding' }, { name: 'Objective Coding' }, { name: 'Functional Coding' }]
const nameParser = (array, objArr) => {
array.forEach(item => {
let names = item.split('$%') // split把字符串分割成字符串数组
let newName = []
// names: progressive, coding
names.forEach(name => {
// name[0]可以访问第一个字符
let nameItem = name[0].toUpperCase() + name.slice(1)
newName.push(nameItem)
})
_objArr.push({
name: newName.join(' ')
})
})
return objArr
}
nameParser(_array, _objArr)
-
得看完整段代码才能清楚具体要干什么
-
存在临时变量,并首尾封闭 -- 不易迭代拓展
names.forEach(name => { let nameItem = name[0].toUpperCase() + name.slice(1); newName.push(nameItem) }) 这里定义的nameItem, 最后会被push到newName中应用下去, 如果后面想修改,肯定要修改这里的代码
2.解决方案 -- 函数化
- 需求分析--数组 => 数组对象
- nameParser => 生成一个数组对象 [ objhelper ]
- objhelper => 组装对象内容{ string: string}
- 功能明确
- objHelper需要key,value => formateName(value值格式化) + assembleobj(组装成obj)
- 功能拆分 原子函数
- formateName => split(字符串拆分) + capitalize(首字母大写) + join(组合)
- assembleobj => 拆不了了
- 代码实现
原子操作
const assembleObj = (key, value) =>{
let obj = {};
obj[key] = value;
return obj;
}
const capitalize = (str) => str[0].toUpperCase()+str.slice[1];
组装方案
const formateName = 组装合并(join(),map(capitlize),split('$%'))
const objHelper = 组装合并(assembleObj('name'), formateName)
const nameParser = map(objHelper)
函数式编程
1.原理
- 加法结合律
- 将耦合的业务逻辑拆成原子函数
- 原子函数通过拼装可以实现之前的功能
- 并且传参和结果保持不变,和以前一样
2.理论思想
转化思维,日常生活中通常是由概括到具体去实现一件事情;
函数式编程则需要先确定每一个最小的点,然后合成事情
a.函数是一等公民
- 函数 -- 逻辑功能实现的落脚点
- 实现原子函数 + 拼接流程
b.声明需求的习惯进行
例如想要实现什么功能,1,2,3...
reacthook就是原子函数,它本身不知道我们要实现的功能,而基于对这些原子函数的使用达到我们想要的效果
c.传参统一成一个(通常上一个函数结果传给下一个函数)
=> 返回一个:惰性函数
=> 传入一个:柯里化
d.纯函数
3.惰性函数
4.纯函数 - 无状态&无副作用
-
无状态 - 多次执行,实现的功能是一样的;不改变传入的参数
-
无副作用 - 函数内部执行,对系统外部 没有影响
const class = {name: 'aaa'} const fun1 = str => class.name = class.name + str; // 违反无副作用,改变了系统内的变量 const fun2 = obj => obj.name += '1'; //违反无状态,修改了传入的参数 const pufun = name => name += '1'; //纯函数 pufun(class.name)
5.流水线组装 - 加工 组装
a.加工,传参处理 - 柯里化
f(x,y,z) => f(x)(y)(z)
const sum = (x, y) => { return x + y }
sum(1, 2);
const sun = (x) => {
return (y) => {
return x + y
}
}
sum(1)(2)
- 手写构造可拆分的传参累加函数 add(1)(2)(3)(4)...
-
构造柯里化结构,多次传参构成传入的参数
-
外层arguments(类数组处理)
-
传入参数无限拓展(递归) => 返回递归函数本身
-
主功能 - 累加
-
组装输出
const add = function(){ // arguments默认为函数调用时 传入的参数 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)
- add(1)返回的是inner函数;
- add(2)调用的是inner,把参数累加到args中
- add(3)同上
- 当要获取值时,就直接 +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)
sum12(1)
具体实现
// 命令式
trim(reverse(toUpperCase(map(arr))))
// 对象式
arr.map().toUpperCase().reverse().trim()
// 函数式
compose(trim, reverse, toUpperCase, map)
pipe()
BOX和函子 functor,高阶对象
// 一封信
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 = mail1.map(function(mail) {
return burn(mail)
})
// 4. 老妈查看
mail3.map(function(mail) {
return momCheck(mail)
})
// 链式
new Mail('love').map(read).map(burn).map(momCheck)