-- 函数式编程
Create by fall on 18 Sep 2021 Recently revised in 25 Jun 2023
函数式编程
functional programming
函数式编程有完善,清洗的原则,我们知晓这些原则后,可以快速读懂代码,定位问题。
使用函数式编程,应该尽可能多的使用函数,而不是程序,二所有的函数必须有接收值,并且和返回值。
示例
这是一个普通的判断函数
function foo(x) {
if (x > 10) return x + 1;
var y = x / 2;
if (y > 3) {
if (x % 2 == 0) return x;
}
if (y > 1) return y;
return x;
}
这样写,可以,但是有些不直观,换成下面的等价书写方式
function foo(x) {
var retValue;
if (retValue == undefined && x > 10) {
retValue = x + 1;
}
var y = x / 2;
if (y > 3) {
if (retValue == undefined && x % 2 == 0) {
retValue = x;
}
}
if (retValue == undefined && y > 1) {
retValue = y;
}
if (retValue == undefined) {
retValue = x;
}
return retValue;
}
后者明显更加容易看懂,这边是函数式编程的一个要点:只有一个出口,该函数通过判断是否为 undefined,来确保只有一个 if
语句执行了该内容。
如果想改变一个值
// 通过一个函数修改变量 y 的值
var y;
function foo(x) {
y = (2 * Math.pow( x, 2 )) + 3;
}
foo( 2 );
y
换一种写法
function foo(x) {
return (2 * Math.pow( x, 2 )) + 3;
}
var y = foo(2)
y
两者有区别吗?一个是操作全局作用域的数据,一个是返回函数作用域的数据。并且,一个是显式输出,一个是隐式输出。
带有 return
就是显式函数,不带就是隐式函数吗?不是。
function sum(list) {
var total = 0;
for (let i = 0; i < list.length; i++) {
if (!list[i]) list[i] = 0; // list 使用了 nums 的引用,不是对 [1,3,9,..] 的值复制,而是引用复制。
total = total + list[i];
}
return total;
}
var nums = [ 1, 3, 9, 27, , 84 ];
sum( nums ); // 124
nums // [1, 3, 9, 27, 0, 84] // 隐式输出改变了
这段代码,除了return的输出,还改变了 nums
的值,这个隐式函数输出在函数式编程中有一个特殊的名称,副作用
没有副作用的函数,就是纯函数。
高阶函数
一个函数如果可以接受,或者返回一个,甚至是多个函数,它被叫做高阶函数。其中最强大的就是闭包。
高阶函数需要保证:
- 使用具名函数
具名函数,有利于语义化代码, 比如说 getPreName()
,操作意图明确,可以回溯问题,防止出现 (anonymous functon)
但是,箭头函数例外,可以进行有效利用,它能优化,简化代码片段中的空间。
- 丢掉 this
this 是函数的一个隐式的输入参数。而我们会进行显式操作,而不是隐式操作,所以丢掉 this
function add(x){
return function(y){
return x+y
}
}
const addPlus10 = add(10)
addPlus10(10)
addPlus10(13)
这种在连续函数调用中指定输入,是函数式编程中非常普遍的形式。
并且可以分成两类,偏函数应用,柯里化
柯里化
将接受多个参数的函数转,转化为只接一个参数的函数
比如说
function add(x){
return function(y){
return x+y
}
}
let a14 = add(14)
a14(10)
a14(5)
add(12)(14)
有什么用呢?就比如说,a14,其实可以利用第一步操作的结果,进行存储,再进行第二步
偏函数
函数参数逐渐减少的过程就是偏应用
实现同一个功能的函数,如果传入的参数更少,那么少的一个函数,被称为多的一方的偏函数
ajax(url,data,callback) => getPerson(data,cb) => getCurrentUser(cb)
// getPerson 是 ajax的偏函数
// getCurrentUser 是 getPerson的偏函数
如果有相当多的函数作为参数,此时
function receiveMutiParam(a,b,c......,x,y,z){
// code
}
// 我们需要封装
function partical(fn,...presetArgs){
return function partiallyApplied(...laterArgs){
return fn(...presetArgs,...laterArgs)
}
}
“随着本系列的继续深入,我们将会把许多函数互相包装起来。记住,这就是函数式编程!” —— 《JavaScript 轻量级函数式编程》
偏函数和柯里化,可以将“指定分离实参”的时机和地方独立开来;
当函数只有一个形参时,我们能够比较容易地组合它们。这种单元函数,便于进行后续的组合函数;
对函数进行包装,使其成为一个高阶函数是函数式编程的精髓!
组合函数
当函数只有一个形参时,我们能够比较容易地组合它们。这种单元函数,便于进行后续的组合函数。
当然,不但可以组合参数,也可以组合函数
整理一下
隐式模式:函数本身操作了函数所在的环境
显式模式:函数本身没有操作函数所在的环境
纯函数:没有隐式函数输出,即没有改变函数所处的外部环境
高阶函数:一个函数,可以接受或者返回多个函数,就是高阶函数
偏应用:函数参数逐渐减少的过程
偏函数:实现同一个功能的函数,如果传入的参数更少,那么少的一个函数,被称为多的一方的偏函数
柯里化:一个函数,每次只传入一个函数,此时被称为函数的柯里化
函数式编程:原本需要传递两个参数才能调用的函数,第一个参数是固定的,可以通过一个函数会根据第一个参数值提供不同内容的函数返回,之后调用,就不用传传递第一个参数了。
应用场景
- 严格控制方法的输入输出(不对函数以外的内容作修改);
- 封装高级函数,动态调整函数的适用范围
- 封装高级函数,将多个方法合并组装,形成新的函数的黑盒;
我个人感觉应用场景很像链式调用,通过返回 this 进行不断操作。但是限制明显比链式调用小很多。
参考文章
作者 | 文章链接 |
---|---|
掘金安东尼 | https://juejin.cn/post/6968259661304692750 |
掘金安东尼 | https://juejin.cn/post/6969016132741103624 |
相关文章
相关文章 | 链接 |
---|---|
JavaScript轻量级函数式编程 | https://github.com/Simingchen/Functional-Light-JS |