JavaScript函数式编程(纯函数、柯里化以及组合函数)
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函数式编程(纯函数、柯里化以及组合函数)的更多相关文章
- javascript柯里化及组合函数~
大家是不是看我上篇博文有点蒙.用的的curry和compose是什么鬼,怎么那么神奇.上篇博文也是主要用到了这两个函数.那今天我们来聊一下curry和compose,这两个东西是函数式编程很重要的东西 ...
- 函数柯里化实现sum函数
需求 实现sum函数,使其可以传入不定长参数,以及不定次数调用 //示例 console.log(sum(1,2)(3)()) //6 console.log(sum(2,3,4,5)(1,2)(3) ...
- React-高阶函数_函数柯里化
高阶函数_函数柯里化 高阶函数(定义) 如果一个函数符合下面两个规范,就是高阶函数: 如果A函数,接收的参数是一个函数,那么A就是一个高阶函数(比如数组方法arr.map()接收的就是一个处理item ...
- 前端面试手写代码——JS函数柯里化
目录 1 什么是函数柯里化 2 柯里化的作用和特点 2.1 参数复用 2.2 提前返回 2.3 延迟执行 3 封装通用柯里化工具函数 4 总结和补充 1 什么是函数柯里化 在计算机科学中,柯里化(Cu ...
- Scala 基础(十二):Scala 函数式编程(四)高级(二)参数(类型)推断、闭包(closure)、函数柯里化(curry)、控制抽象
1 参数(类型)推断 参数推断省去类型信息(在某些情况下[需要有应用场景],参数类型是可以推断出来的,如list=(1,2,3) list.map() map中函数参数类型是可以推断的),同时也可以 ...
- Javascript函数柯里化(curry)
函数柯里化currying,是函数式编程非常重要的一个标志.它的实现需要满足以下条件,首先就是函数可以作为参数进行传递,然后就是函数可以作为返回值return出去.我们依靠这个特性编写很多优雅酷炫的代 ...
- JavaScript中的事件循环机制跟函数柯里化
一.事件循环机制的理解 test();//按秒输出5个5 function test() { for (var i = 0; i < 5; i++) { setTimeout(() => ...
- 【React】学习笔记(一)——React入门、面向组件编程、函数柯里化
课程原视频:https://www.bilibili.com/video/BV1wy4y1D7JT?p=2&spm_id_from=pageDriver 目录 一.React 概述 1.1.R ...
- JavaScript函数柯里化
函数式 JavaScript是以函数为一等公民的函数式语言.函数在JavaScript中也是一个对象(继承制Function),函数也可以作为参数传递成函数变量.最近几年函数式也因为其无副作用的特性. ...
随机推荐
- 面试官:为什么 TCP 三次握手期间,客户端和服务端的初始化序列号要求不一样?
大家好,我是小林. 为什么 TCP 三次握手期间,客户端和服务端的初始化序列号要求不一样的呢? 接下来,我一步一步给大家讲明白,我觉得应该有不少人会有类似的问题,所以今天在肝一篇! 正文 为什么 TC ...
- 痞子衡嵌入式:在IAR开发环境下将整个源文件代码重定向到任意RAM中的方法
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是在IAR开发环境下将整个源文件代码重定向到任意RAM中的方法. 痞子衡旧文 <在IAR下将关键函数重定向到RAM中执行的方法> ...
- Linux命令(2)--cp拷贝、mv剪切、head、tail追踪、tar归档
文章目录 一.知识回顾 ls cd 二.Linux基本操作(二) 1.cp 拷贝 2.mv 移动(剪切) 3.head 头部 4.tail 追踪(尾部) 5.tar 归档 查看 压缩 解压 总结 一. ...
- k8s-pv-pvc
1. 简介 持久卷(PersistentVolume,PV)是集群中的一块存储,可以由管理员事先供应,或者 使用存储类(Storage Class)来动态供应. 持久卷是集群资源,就像节点也是集群资源 ...
- java原码、反码、补码、位运算
1.对于有符号的数(java中的数都是有符号的) 二进制的最高位是符号位:0表示正数,1表示负数 正数的原码,反码,补码都一样 负数的反码=它的原码符号位不变,其它位取反 负数的补码=它的反码+1 0 ...
- golang中值类型的嵌入式字段和指针类型的嵌入式字段
总结: 1. 值类型的嵌入式字段,该类型拥有值类型的方法集,没有值指针类型的方法集 2. 指针类型的嵌入式字段,该类型拥有值指针类型的方法集,没有值类型的方法集,并且,该类型的指针类型也有值指针类型的 ...
- NFS数据共享(全面讲解使用教程)
目录 一:NFS数据共享 1.NFS简介: 2.什么是NFS? 3.NFS的应用 二:NFS数据共享实践 二:NFS配置详解 1.控制文件权限 三:配置文件分类 四:NFS统一用户 1.创建用户(客户 ...
- docker和K8s对应参数
创建 Pod 时设置命令及参数 创建 Pod 时,可以为其下的容器设置启动时要执行的命令及其参数.如果要设置命令,就填写在配置文件的 command 字段下,如果要设置命令的参数,就填写在配置文件的 ...
- 谷歌CEO桑达尔·皮查伊:区块链可能撼动云计算
谷歌CEO桑达尔·皮查伊在周二的季度收益电话会议上承认了Web3和区块链的力量. 皮查伊表示,Web3描述了基于区块链的互联网新愿景,区块链是一种分散.安全.透明的技术,支持加密货币网络.不可替代代币 ...
- NOIP2017 Day2T3 列队
首先可以观察到这样一个事实,如果 \((x, y)\) 出队,那么只会影响 \(x\) 这一行,以及最后一列的排布.并且可以发现,每次一个人出队,总会对最后一列有影响,因此我们可能需要将最后一列单独拿 ...