JavaScript函数式编程(纯函数、柯里化以及组合函数)

前言

函数式编程(Functional Programming),又称为泛函编程,是一种编程范式。早在很久以前就提出了函数式编程这个概念了,而后面一直长期被面向对象编程所统治着,最近几年函数式编程又回到了大家的视野中,JavaScript是一门以函数为第一公民的语言,必定是支持这一种编程范式的,下面就来谈谈JavaScript函数式编程中的核心概念纯函数、柯里化以及组合函数。

1.纯函数

1.1.纯函数的概念

对于纯函数的定义,维基百科中是这样描述的:在程序设计中,若函数符合以下条件,那么这个函数被称之为纯函数。

  • 此函数在相同的输入值时,需产生相同的输出
  • 函数的输入和输出值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关
  • 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等;

对以上描述总结就是:

  • 对于相同的输入,永远会得到相同的输出;
  • 在函数的执行过程中,没有任何可观察的副作用;
  • 同时也不依赖外部环境的状态;

1.2.副作用

上面提到了一个词叫“副作用”,那么什么是副作用呢?

  • 通常我们所说的副作用大多数是指药会产生的副作用;
  • 而在计算机科学中,副作用指在执行一个函数时,除了得到函数的返回值以外,还在函数调用时产生了附加的影响,比如修改了全局变量的状态,修改了传入的参数或得到了其它的输出内容等;

1.3.纯函数案例

  • 编写一个求和的函数sum,只要我们输入了固定的值,sum函数就会给我们返回固定的结果,且不会产生任何副作用。

    function sum(a, b) {
    return a + b
    } const res = sum(10, 20)
    console.log(res) // 30
  • 以下的sum函数虽然对于固定的输入也会返回固定的输出,但是函数内部修改了全局变量message,就认定为产生了副作用,不属于纯函数。

    let message = 'hello'
    function sum(a, b) {
    message = 'hi'
    return a + b
    }
  • 在JavaScript中也提供了许多的内置方法,有些是纯函数,有些则不是。像操作数组的两个方法slice和splice。

    • slice方法就是一个纯函数,因为对于同一个数组固定的输入可以得到固定的输出,且没有任何副作用;

      const nums = [1, 2, 3, 4, 5]
      const newNums = nums.slice(1, 3)
      console.log(newNums) // [2, 3]
      console.log(nums) // [ 1, 2, 3, 4, 5 ]
    • splice方法不是一个纯函数,因为它改变了原数组nums;

      const nums = [1, 2, 3, 4, 5]
      const newNums = nums.splice(1, 3)
      console.log(newNums) // [ 2, 3, 4 ]
      console.log(nums) // [ 1, 5 ]

2.柯里化

2.1.柯里化的概念

对于柯里化的定义,维基百科中是这样解释的:

  • 柯里化是指把接收多个参数的函数,变成接收一个单一参数(最初函数的第一个参数)的函数,并且返回接收余下的参数,而且返回结果的新函数的技术;
  • 柯里化声称“如果你固定某些参数,你将得到接受余下参数的一个函数”

总结:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数的过程就称之为柯里化。

2.2.函数柯里化的过程

编写一个普通的三值求和函数:

function sum(x, y, z) {
return x + y + z
} const res = sum(10, 20, 30)
console.log(res) // 60

将以上求和函数柯里化得:

  • 将传入的三个参数进行拆解,依次返回一个函数,并传入一个参数;
  • 在保证同样功能的同时,其调用方式却发生了变化;
  • 注意:在拆解参数时,不一定非要将参数拆成一个个的,也可以拆成2+1或1+2;
function sum(x) {
return function(y) {
return function(z) {
return x + y + z
}
}
} const res = sum(10)(20)(30)
console.log(res)

使用ES6箭头函数简写为:

const sum = x => y => z => x + y + z

2.3.函数柯里化的特点及应用

  • 让函数的职责更加单一。柯里化可以实现让一个函数处理的问题尽可能的单一,而不是将一大堆逻辑交给一个函数来处理。

    • 将上面的三值求和函数增加一个需求,在计算结果之前给每个值加上2,先看看不使用柯里化的实现效果:

      function sum(x, y, z) {
      x = x + 2
      y = y + 2
      z = z + 2
      return x + y + z
      }
    • 柯里化的实现效果:

      function sum(x) {
      x = x + 2
      return function(y) {
      y = y + 2
      return function(z) {
      z = z + 2
      return x + y + z
      }
      }
      }
    • 很明显函数柯里化后,让我们对每个参数的处理更加单一

  • 提高函数参数逻辑复用。同样使用上面的求和函数,增加另一个需求,固定第一个参数的值为10,直接看柯里化的实现效果吧,后续函数调用时第一个参数值都为10的话,就可以直接调用sum10函数了。

    function sum(x) {
    return function(y) {
    return function(z) {
    return x + y + z
    }
    }
    } const sum10 = sum(10) // 指定第一个参数值为10的函数
    const res = sum10(20)(30)
    console.log(res) // 60

2.4.自动柯里化函数的实现

function autoCurrying(fn) {
// 1.拿到当前需要柯里化函数的参数个数
const fnLen = fn.length // 2.定义一个柯里化之后的函数
function curried_1(...args1) {
// 2.1拿到当前传入参数的个数
const argsLen = args1.length // 2.1.将当前传入参数个数和fn需要的参数个数进行比较
if (argsLen >= fnLen) {
// 如果当前传入的参数个数已经大于等于fn需要的参数个数
// 直接执行fn,并在执行时绑定this,并将对应的参数数组传入
return fn.apply(this, args1)
} else {
// 如果传入的参数不够,说明需要继续返回函数来接收参数
function curried_2(...args2) {
// 将参数进行合并,递归调用curried_1,直到参数达到fn需要的参数个数
return curried_1.apply(this, [...args1, ...args2])
} // 返回继续接收参数函数
return curried_2
}
} // 3.将柯里化的函数返回
return curried_1
}

测试:

function sum(x, y, z) {
return x + y + z
} const curryingSum = autoCurrying(sum) const res1 = curryingSum(10)(20)(30)
const res2 = curryingSum(10, 20)(30)
const res3 = curryingSum(10)(20, 30)
const res4 = curryingSum(10, 20, 30)
console.log(res1) // 60
console.log(res2) // 60
console.log(res3) // 60
console.log(res4) // 60

3.组合函数

组合函数(Compose Function)是在JavaScript开发过程中一种对函数的使用技巧、模式。对某一个数据进行函数调用,执行两个函数,这两个函数需要依次执行,所以需要将这两个函数组合起来,自动依次调用,而这个过程就叫做函数的组合,组合形成的函数就叫做组合函数。

需求:对一个数字先进行乘法运算,再进行平方运算。

  • 一般情况下,需要先定义两个函数,然后再对其依次调用:

    function double(num) {
    return num * 2
    }
    function square(num) {
    return num ** 2
    } const duobleResult = double(10)
    const squareResult = square(duobleResult)
    console.log(squareResult) // 400
  • 实现一个组合函数,将duoble和square两个函数组合起来:

    function composeFn(fn1, fn2) {
    return function(num) {
    return fn2(fn1(num))
    }
    } const execFn = composeFn(double, square)
    const res = execFn(10)
    console.log(res) // 400

实现一个自动组合函数的函数:

function autoComposeFn(...fns) {
// 1.拿到需要组合的函数个数
const fnsLen = fns.length // 2.对传入的函数进行边界判断,所有参数必须为函数
for (let i = 0; i < fnsLen; i++) {
if (typeof fns[i] !== 'function') {
throw TypeError('The argument passed must be a function.')
}
} // 3.定义一个组合之后的函数
function composeFn(...args) {
// 3.1.拿到第一个函数的返回值
let result = fns[0].apply(this, args) // 3.1.判断传入的函数个数
if (fnsLen === 1) {
// 如果传入的函数个数为一个,直接将结果返回
return result
} else {
// 如果传入的函数个数 >= 2
// 依次将函数取出进行调用,将上一个函数的返回值作为参数传给下一个函数
// 从第二个函数开始遍历
for (let i = 1; i < fnsLen; i++) {
result = fns[i].call(this, result)
} // 将结果返回
return result
}
} // 4.将组合之后的函数返回
return composeFn
}

测试:

function double(num) {
return num * 2
}
function square(num) {
return num ** 2
} const composeFn = autoComposeFn(double, square)
const res = composeFn(10)
console.log(res) // 400

JavaScript函数式编程(纯函数、柯里化以及组合函数)的更多相关文章

  1. javascript柯里化及组合函数~

    大家是不是看我上篇博文有点蒙.用的的curry和compose是什么鬼,怎么那么神奇.上篇博文也是主要用到了这两个函数.那今天我们来聊一下curry和compose,这两个东西是函数式编程很重要的东西 ...

  2. 函数柯里化实现sum函数

    需求 实现sum函数,使其可以传入不定长参数,以及不定次数调用 //示例 console.log(sum(1,2)(3)()) //6 console.log(sum(2,3,4,5)(1,2)(3) ...

  3. React-高阶函数_函数柯里化

    高阶函数_函数柯里化 高阶函数(定义) 如果一个函数符合下面两个规范,就是高阶函数: 如果A函数,接收的参数是一个函数,那么A就是一个高阶函数(比如数组方法arr.map()接收的就是一个处理item ...

  4. 前端面试手写代码——JS函数柯里化

    目录 1 什么是函数柯里化 2 柯里化的作用和特点 2.1 参数复用 2.2 提前返回 2.3 延迟执行 3 封装通用柯里化工具函数 4 总结和补充 1 什么是函数柯里化 在计算机科学中,柯里化(Cu ...

  5. Scala 基础(十二):Scala 函数式编程(四)高级(二)参数(类型)推断、闭包(closure)、函数柯里化(curry)、控制抽象

    1  参数(类型)推断 参数推断省去类型信息(在某些情况下[需要有应用场景],参数类型是可以推断出来的,如list=(1,2,3) list.map() map中函数参数类型是可以推断的),同时也可以 ...

  6. Javascript函数柯里化(curry)

    函数柯里化currying,是函数式编程非常重要的一个标志.它的实现需要满足以下条件,首先就是函数可以作为参数进行传递,然后就是函数可以作为返回值return出去.我们依靠这个特性编写很多优雅酷炫的代 ...

  7. JavaScript中的事件循环机制跟函数柯里化

    一.事件循环机制的理解 test();//按秒输出5个5 function test() { for (var i = 0; i < 5; i++) { setTimeout(() => ...

  8. 【React】学习笔记(一)——React入门、面向组件编程、函数柯里化

    课程原视频:https://www.bilibili.com/video/BV1wy4y1D7JT?p=2&spm_id_from=pageDriver 目录 一.React 概述 1.1.R ...

  9. JavaScript函数柯里化

    函数式 JavaScript是以函数为一等公民的函数式语言.函数在JavaScript中也是一个对象(继承制Function),函数也可以作为参数传递成函数变量.最近几年函数式也因为其无副作用的特性. ...

随机推荐

  1. Word2010发布博客

    原文链接: https://www.toutiao.com/i6488986125292536334/ 选择"文件按钮","保存并发送"菜单项,"发布 ...

  2. 学习笔记--Java中的变量

    Java中的变量 /** * 关于 Java 语言当中的变量: * * 1. 什么是变量? * - 变量的本质上来说是内存空间,这块空间有(数据类型.名字.字面值) * - 变量包括三部分:数据类型. ...

  3. SYCOJ2205超级百钱百鸡

    题目-超级百钱百鸡 (shiyancang.cn) 百钱百鸡的加强版 百钱百鸡的话,因为是有范围,所以挨个挨个尝试即可,确定两个,即可确定第三个. 超级百钱百鸡,通过题目的描述,最后可以得到一个二元的 ...

  4. Java安全之Spring内存马

    Java安全之Spring内存马 基础知识 Bean bean 是 Spring 框架的一个核心概念,它是构成应用程序的主干,并且是由 Spring IoC 容器负责实例化.配置.组装和管理的对象. ...

  5. testng.xml 执行多个测试用例

    1.在工程名字上点击右键,点击[New]-->[File] 2.在弹出的[New File]对话框中的[File name]输入[testng.xml],点击[Finish]即创建了一个test ...

  6. boot项目打包剔除配置文件(打包优化)

    背景: 最近在项目开发中,在本地开发和线上部署的时候总是切换dev和pro环境,项目多了改起来还是很麻烦的,以下记录下boot项目的打包优化,打包的时候剔除配置文件,然后将配置文件手动放到线上,线上项 ...

  7. 贝塞尔曲线(面)二三维可视化(Three+d3)

    贝塞尔曲线(面)二三维可视化(Three+d3) 在学完 games101 几何后开始实践,可视化贝塞尔曲线 我想实现三维的贝塞尔曲线,用 threejs,但是 threejs 控制太麻烦了,因此,我 ...

  8. [论文翻译] 分布式训练 Parameter Sharding 之 Google Weight Sharding

    [论文翻译] 分布式训练 Parameter sharding 之 Google Weight Sharding 目录 [论文翻译] 分布式训练 Parameter sharding 之 Google ...

  9. Linux命令(2)--cp拷贝、mv剪切、head、tail追踪、tar归档

    文章目录 一.知识回顾 ls cd 二.Linux基本操作(二) 1.cp 拷贝 2.mv 移动(剪切) 3.head 头部 4.tail 追踪(尾部) 5.tar 归档 查看 压缩 解压 总结 一. ...

  10. golang中gomodule讲解

    0. GOMODULES模式 1. GOPATH的缺点 1. 无版本控制概念 2. 无法同步一致第三方版本号 3. 无法指定当前项目引用的第三方版本号 2. go1.11版本之后可以使用GoModul ...