跳到主要内容

-- 函数式编程

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