【Javascript修炼篇】JS中的函数式编程
介绍:
函数式编程(FP)是一种编程范式,这意味着一种基于一些原则来思考软件构建的方法,比如 纯函数、不可变性、一等与高阶函数、函数组合、闭包、声明式编程、递归、引用透明性、柯里化 和 部分应用。
当这些原则有效地应用到 JavaScript 中时,可以使得代码更加模块化、可维护、健壮、易于理解、可测试,并且能够优雅地处理复杂的问题。
这篇文章看起来可能有点长,但不会那么理论化。
让我们开始逐一实验吧:
1. 纯函数:
两条规则:
- 给定相同的输入,总是返回相同的结果。
- 不产生副作用。
用处: 容易重构,使代码更具灵活性和适应性。
例子 1:
// 不纯的函数。
let a = 4;
const multiplyNumbers = (b) => a *= b;multiplyNumbers(3);
console.log(a); // 第一次调用:12
> 12
multiplyNumbers(3);
console.log(a); // 第二次调用:36
> 36// 修改了外部变量,所以不是纯函数。
// 纯函数版本。
const multiplyNumbers = (x,y) => x * y;multiplyNumbers(2, 3);
> 6
例子 2:
// 不纯的函数。
addNumberarr = (arr, num) => {
arr.push(num);
};
const testArr = [1,2,3];
addNumberarr(testArr, 4);console.log(testArr);
> [1, 2, 3, 4]// 修改了输入数组,所以不是纯函数。
// 上面的纯函数版本。
addNumberarr = (arr, num) => {
return [...arr, num];
};
const testArr = [1,2,3];
addNumberarr(testArr, 4);
> [1, 2, 3, 4]
JS 内置的纯函数:
arr.reduce()
arr.map()
arr.filter()
arr.concat()
arr.slice()
arr.each()
arr.every()
... - 扩展语法
JS 内置的非纯函数:
arr.splice()
arr.push()
arr.sort()
Math.random()
2. 不可变性:
一旦创建就不能改变状态的对象。
一个简单的例子就是使用 slice 方法来帮助你轻松理解它的含义。
const arr = [1,2,3,4];const slicedArray = arr.slice(1,2);slicedArray
> [2]arr
> [1, 2, 3, 4]
如果你看上面的例子,slice 并没有改变原始数组 arr。而下面的例子则不同:
const arr = [1,2,3,4];arr.push(5);
> 5arr
> [1, 2, 3, 4, 5]
原始数组 arr 被修改了。这并不是说我们不应该使用 push,但是在大多数情况下我们可以避免这种情况。一个简单的例子是:
const arr = [1,2,3,4];const newArr = [...arr, 5];arr
> [1, 2, 3, 4]newArr
> [1, 2, 3, 4, 5]
上面的所有都是简单例子,可能不会造成任何问题。但是,如果我们在整个文件中尽可能多地修改同一个对象,就会带来许多问题。因为我们需要跟踪这个对象被修改了多少次以及以何种方式被修改。
为了解决这个问题,我们需要避免修改对象。
3. 一等函数
一等函数是指把函数当作一等公民的概念,意味着它们被视为常规变量或值。这让函数可以像字符串或数字等其他数据类型一样被操作和使用。这允许函数作为参数传递给其他函数,从其他函数返回值,以及被赋值给变量。JavaScript 支持这一点。
它打开了强大的编程技术的大门,比如高阶函数、函数组合,以及抽象的创建。
4. 高阶函数:
一个函数可以接受另一个函数作为参数或者返回一个函数作为结果,这样的函数被称为高阶函数。
- 返回一个函数的函数
const higherOrderFunc = function() {return function() {return 12;}
}// 返回下面的函数,所以它是高阶函数。
higherOrderFunc();
> ƒ () {return 12;}higherOrderFunc()();
> 12
- 接受一个函数作为参数的函数
const testFunc = function(x) {return x + 12;
}// 接受函数作为参数。
const higherOrderFunc = function(testFunc) {return testFunc(8);
}higherOrderFunc(testFunc);
> 20
例子 1:
function calculate(operation, numbers) {return operation(numbers);
}function addition(numbers) {let sum = 0;for (const number of numbers) {sum+=number;}return sum;
}function multiply(numbers) {let sum = 1;for (const number of numbers) {sum*=number;}return sum;
}const numbers = [1,2,3,4,5];
console.log(calculate(addition, numbers));
> 15console.log(calculate(multiply, numbers));
> 120// calculate(multiply, numbers) - 传递函数作为参数时不加括号。
高阶函数的好处:
减少代码重复
单一职责
在 JavaScript 中,函数可以接受原始类型或对象作为参数并返回相同类型,称为一阶函数。
JS 内置的高阶函数有:
arr.reduce(), arr.forEach(), arr.filter(), arr.map()
5. 函数组合:
这是一种方法,其中将一个函数的结果传给下一个函数。
const add = (x, y) => x+y;const subtract = (x) => x-4;const multiply = (x) => x * 8;// add 的结果传给 subtract,其结果再传给 multiply。
const result = multiply(subtract(add(2, 3)));result;
> 8
看起来很清晰,但如果我们要一个接一个地调用更多函数会怎么样呢?让我们试试更干净的方法。
const compose = (...functions) => x => functions.reduceRight((total, f) => f(total), x);const add = x => x+2;const subtract = x => x-1;const multiply = x => x * 8;compose(multiply, subtract, add)(2);
> 24
我们也可以使用 reduce 来实现:
const pipe = (...functions) => x => functions.reduce((total, f) => f(total), x);const add = x => x+2;const subtract = x => x-1;const multiply = x => x * 8;pipe(add, subtract, multiply)(2);
> 24
pipe - 从左到右执行。
compose - 从右到左执行。
6. 声明式编程:
声明式: 告诉 做什么
命令式: 告诉 怎么做
例子: 找出部门为 ‘justCode’ 的员工及其工资总和。
命令式风格:
const employees = [
{id: 1, name: 'james', dept: 'admin', salary: 10000},
{id: 1, name: 'Tom', dept: 'finance', salary: 10000},
{id: 1, name: 'peter', dept: 'justCode', salary: 12500},
{id: 1, name: 'tunner', dept: 'justCode', salary: 14500},
];const justCodeDept = [];// 根据部门名称筛选员工。
for (let i=0; i<employees.length; i++) {if (employees[i].dept === 'justCode') {justCodeDept.push(employees[i]);}
}// 计算 justCodeDept 员工的工资总和。
let summation = 0;
for (j = 0; j<justCodeDept.length; j++) {summation = summation + justCodeDept[j].salary;
}console.log(summation);
声明式风格:
const employees = [
{id: 1, name: 'james', dept: 'admin', salary: 10000},
{id: 1, name: 'Tom', dept: 'finance', salary: 10000},
{id: 1, name: 'peter', dept: 'justCode', salary: 12500},
{id: 1, name: 'tunner', dept: 'justCode', salary: 14500},
];console.log(employees.filter(item => item.dept === 'justCode').reduce(((previousValue, currentValue) => previousValue += currentValue.salary), 0));
7. 柯里化:
将接收多个参数的函数拆分成一系列函数,每个函数只接收单个参数。
例子 1:
通常我们写:
function addition(x, y, z) {return x + y + z;
}addition(1, 2, 3);
> 6
柯里化版本:
function addition(x) {return function addY(y) {return function addZ(z) {return x + y + z;}}
}addition(1)(2)(3);
> 6
使用箭头函数:
addition = (x) => (y) => (z) => x + y + z;addition(1)(2)(3);
> 6
例子 2:
function formWelcomNote(name) {name = `Hello ${name}, `;return function(location) {location = `Welcome to ${location},`;return function(section) {return `${name}${location} Please visit ${section} section`}}
}formWelcomNote('Yester')('VK Just Code Articles')('JS Articles');
> 'Hello Yester, Welcome to VK Just Code Articles, Please visit JS Articles section'
我们也可以这样写:
formWelcomNote = (name) => {name = `Hello ${name}, `;return (location) => {location = `Welcome to ${location},`;return (section) => {return `${name}${location} Please visit ${section} section`}}
}formWelcomNote('Yester')('VK Just Code Articles')('JS Articles');
> 'Hello Yester, Welcome to VK Just Code Articles, Please visit JS Articles section'
例子 3:
function calculation(fn) {switch (fn) {case 'add': return (a, b) => a + b;case 'sub': return (a, b) => a - b;case 'mul': return (a, b) => a * b;case 'div': return (a, b) => a / b;}
}
console.log(calculation('mul')(4, 2));
8. 部分应用:
你为一个函数固定一定数量的参数,并生成一个新的带有较少参数的函数。这个新函数可以在稍后的时间用剩下的参数来调用。部分应用有助于创建更加专业和可重用的函数。
例子:
function add(a, b) {return a + b;
}// 部分应用第一个参数
const add2 = add.bind(null, 2);console.log(add2(5)); // 输出:7 (2 + 5)
console.log(add2(8)); // 输出:10 (2 + 8)
9. 引用透明性:
JavaScript 中的一个表达式可以用它的值来替代,这种特性叫做引用透明性。
const add = (x, y) => x + y;const multiply = (x) => x * 4;// add (3, 4) 可以被替换为 7 —— 引用透明性。multiply(add(3, 4));
> 28multiply(add(3, 4));
> 28
const arr = [];
const add = (x, y) => {const addition = x + y;arr.push(addition);return addition;
}const multiply = (x) => x * 4;// 在这里,我们不能用 7 替换 add(3, 4),因为它会影响程序逻辑。
multiply(add(3, 4));
> 28multiply(add(3, 4));
> 28
10. 闭包:
闭包让你可以从内部函数访问外部函数的作用域。
function outer() {const name = 'test';function inner() {// 'name' 从外部函数可以访问到内部函数中console.log(name);}inner();
}
outer();> test
function outerAdd(x) {return function(y) {return x + y;};
}const outer12 = outerAdd(12); // x 为 12.
const outer14 = outerAdd(14); // x 为 14.const outer12Result = outer12(12); // y 为 12.
console.log(outer12Result);
> 24const outer14Result = outer14(14); // y 为 14.
console.log(outer14Result);
> 28
或者,你也可以像下面这样使用箭头函数:
outerAdd = x => y => x + y;const outer12 = outerAdd(12);
const outer14 = outerAdd(14);const outer12Result = outer12(12);
console.log(outer12Result);
> 24const outer14Result = outer14(14);
console.log(outer14Result);
> 28
使用闭包的计数器示例:
function outer() {let counter = 0;return function inner() {counter += 1;return counter;}
}
const out = outer();console.log(out());
console.log(out());
console.log(out());> 1
> 2
> 3
11. 递归:
递归是一种编程技巧,在其中函数通过自我调用来解决问题。
例子:
function factorial(n) {if (n === 0 || n === 1) {return 1;} else {return n * factorial(n - 1);}
}console.log(factorial(5)); // 输出:120 (5 * 4 * 3 * 2 * 1)
console.log(factorial(0)); // 输出:1 (按定义)
在这个例子中,factorial 函数计算给定数 n 的阶乘。它使用了 n === 0 和 n === 1 的基本情况,阶乘定义为 1。对于其它任何值的 n,函数递归地调用自身,并将结果乘以 n。
当你调用 factorial(5) 时,递归调用序列如下所示:
factorial(5)-> 5 * factorial(4)-> 4 * factorial(3)-> 3 * factorial(2)-> 2 * factorial(1)-> 1<- 2 * 1 = 2<- 3 * 2 = 6<- 4 * 6 = 24<- 5 * 24 = 120
如果有任何概念上的例子需要补充,请随时评论。
希望通过今天这篇文章,让你对JS中的函数式编程有了更好的理解。并且可以在日常的开发过程中进行灵活应用,以提高开发效率。
相关文章:

【Javascript修炼篇】JS中的函数式编程
介绍: 函数式编程(FP)是一种编程范式,这意味着一种基于一些原则来思考软件构建的方法,比如 纯函数、不可变性、一等与高阶函数、函数组合、闭包、声明式编程、递归、引用透明性、柯里化 和 部分应用。 当这些原则有效…...

spring cxf 常用注解
在Spring框架中,特别是当与Apache CXF(一个流行的SOAP和RESTful Web服务框架)结合使用时,我们会遇到一系列的注解。以下是一些在Spring和CXF中常用的注解: Spring相关注解: Component:用于定义一…...

python | x-y 网格切片
写在前面 通常, 我们处理的毕竟完善的nc产品,一般呈现未timexlatxlon的维度,且lon和lat都是规则的网格,我们可以方便的使用xarray.sel()选择合适的区域进行切片。但是,部分nc产品比如卫星轨道或者模式输出的数据&…...

【C#】vs2022 .net8
Visual Studio 2022 IDE - 适用于软件开发人员的编程工具 (microsoft.com) 更新就会出现...

【华为杯】第二十一届中国研究生数学建模竞赛
“华为杯”第二十一届中国研究生数学建模竞赛即将开始,梦想科研社给大家整理一些比赛信息,在正式开赛后,我们也会持续分享一些课题的分析以及代码,有需要的可以联系我们获取资料信息哦 一、时间节点 1.加密赛题开始下载时间&…...

首次开机android.intent.action.BOOT_COMPLETED开机广播发送慢的问题
1. 背景 做过android开发的同学相信一定做个这种逻辑:app接收BOOT_COMPLETED开机广播,自启动,或者收到广播做一些事情。目前在我们的项目上遇到首次开机,BOOT_COMPLETED开机广播发送慢的问题。接下来分享记录下如何定位这类问题。 2. 分析过…...

通信工程学习:什么是OLT光线路终端
OLT:光线路终端 OLT(Optical Line Terminal,光线路终端)是光纤通信系统中的核心局端设备,特别是在无源光网络(Passive Optical Network, PON)架构中扮演着至关重要的角色。以下是关于OLT光线路终…...

Unity的Button组件进行扩展
废话不多说,在Untiy中,如果想要对Button等组件进行扩展的话,那么不仅仅只需要将新增的属性设置为public或者增加SerializeField字段就行了的,同时需要对Inspector的GUI面板进行修改,以下直接展示代码: usi…...

前端vue-插值表达式和v-html的区别
创建vue实例的时候,可以有两种形式。 1.let appnew Vue({}) 2 const appnew Vue({}) 3 el是挂载点,是上面div的id值 4 data中的值可以展示在上面div中 5 v-html标签里面如果有内容,则我们的新内容会把标签里面的内容覆盖掉...

【开发心得】筑梦上海:项目风云录(4)
不知不觉已经写到了第4篇,天下大事,必作于细。 其实项目管理也是如此,成功都在细节之处。自从博士离开以后,项目逐步开始进入了正常轨道。来来回回的30多人,也不能一一列举的记流水账。 目录 会海和MSN 小娇往事 …...

el-table使用el-switch选择器没效果
出现问题的代码: 0表示启用,1表示禁用,发现页面根本没有效果,百思不得其解,查阅资料,恍然大悟。 <el-table :data"userList" stripe border style"width: 100%" height"500"><…...

libserailport交叉编译适配说明
1:libserialport简介 github路径 自己的gitee路径 libserialport 是一个跨平台的串口通信库,由 sigrok 项目开发。它简洁、易用,并且支持多种操作系统。 libserialport 支持阻塞和非阻塞模式,可以根据你的需求选择适当的模式。阻…...

C语言中的一些小知识(二)
一、"%"运算符两侧只能是整数 在C语言中,% 运算符称为模运算符或取余运算符,它用于计算两个整数相除后的余数。当使用 % 运算符时,操作数必须是整数类型(包括 char、int、long 等)。 语法 remainder div…...

使用 Go 语言实现简单聊天系统
在互联网时代,聊天系统是常见的应用场景之一。无论是即时通讯、在线客服还是多人游戏中的消息系统,聊天功能的实现都是必不可少的。本文将使用 Go 语言,结合 WebSocket 来构建一个简单的多人聊天室系统。 一、项目结构 首先,我们…...

用友U8二次开发工具KK-FULL-*****-EFWeb使用方法
1、安装: 下一步,下一步即可。弹出黑框不要关闭,让其自动执行并关闭。 2、服务配置: 输入服务器IP地址,选择U8数据源,输入U8用户名及账号,U8登录日期勾选系统日期。测试参数有效性,提示测试通过…...

【经验帖】脏读和不可重复读的概念及影响
脏读和不可重复读是数据库事务并发执行时可能出现的两种数据一致性问题,它们对数据的一致性和完整性有着显著的影响。以下是脏读和不可重复读的具体影响: 脏读的影响 脏读发生在一个事务读取了另一个事务未提交的数据时。由于这些数据尚未被提交&#x…...

MTK zephyr平台:USB升级、枚举流程
一、USB升级流程 通过代码及log分析,当前平台升级过程在PL阶段进行 USB download相关代码 mtk/modules/hal/boot/preloader/platform/flashc/ mtk/modules/hal/boot/preloader/platform/board_name/flash/ mtk/modules/hal/boot/preloader/platform/board_name/src/drive…...

golang操作mysql利器-gorm
1、傻瓜示例 GORM通过将数据库表中的数据映射到面向对象的模型中,简化了数据库操作,使得开发者可以很方便的使用代码来操作数据库,而无需编写SQL语句。 目前有个mysql表:miniprogram_orders,其存储了所有用户对应的订…...

09 Shell Scriptfor循环结构语句
Shell Scriptfor循环结构语句 一、Shell FOR循环语句概述 属于shell的符合语句 可以看出帮助信息给出了两种语法 [rootlocalhost ~]# help for for: for NAME [in WORDS ... ] ; do COMMANDS; doneExecute commands for each member in a list.The for loop executes…...

【Java】并发集合
并发集合(java.util.concurrent) 一、List CopyOnWriteArrayList(ReentrantLock实现线程安全) (1)并发修改(写操作)时保证线程安全: 通过ReentrantLock实现多个线程并…...

活动邀请|景联文科技与您相约华为全联接大会2024
2024年9月19-21日,第九届华为全联接大会(简称:HUAWEICONNECT2024)将在上海世博展览馆和上海世博中心举办。 作为华为的旗舰盛会,本次大会以“共赢行业智能化”为主题将邀请思想领袖、商业精英、技术专家、合作伙伴、开发者等业界同仁…...

周边游|基于springBoot的周边游平台设计与实现(附项目源码+论文+数据库)
私信或留言即免费送开题报告和任务书(可指定任意题目) 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 在如今社会上,关于信息上面的处理,没有任…...

【编程基础知识】mysql是怎样执行一条sql语句的,涉及到哪些环节步骤是,mysql的整体体系结构是啥样的,有哪些组件
一、步骤 MySQL执行一条SQL语句的过程涉及多个环节和步骤。以下是这一过程的概述: 客户端连接:客户端通过连接器(Connector)向MySQL服务器发起连接请求。身份验证:连接器对用户身份进行验证,确保用户有权…...

如何上传tauri项目到csdn gitcode
如何上传tauri项目到csdn gitcode 首先保证项目目录有.gitignore,避免不必要的文件上传分享。 gitignore文件 # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log*node_modules dist dist-ssr *.local# Editor …...

【速成Redis】02 Redis 五大基本数据类型常用命令
前言: 上一节课,我们对redis进行了初步了解,和安装好了redis。【速成Redis】01 Redis简介及windows上如何安装redishttps://blog.csdn.net/weixin_71246590/article/details/142319358?spm1001.2014.3001.5501 该篇博客,我们正…...

UnLua扩展C++函数和蓝图自定义事件
一、通过BlueprintImplementableEvent标记扩展C函数 1、 这个标记表示C不需要实现,让蓝图/Lua重写。 2、首先在C中将LuaImp函数标记为BlueprintImplementableEvent,不需要实现,然后再GetIndex中调用该函数。 MyBaseActor.h UFUNCTION(Bluepr…...

干耳屎硬掏不出来怎么办?质量最好的可视挖耳勺推荐
很多干耳的小伙伴都会用普通耳勺来掏耳朵。由于普通耳勺由于其盲操作的特性,对于耳道非直线结构的清理存在诸多不便。所以市面上出现了可视挖耳勺,让我们清晰的看到自己耳道,更加安全的清洁耳朵。,可视挖耳勺这款产品在市场上越来…...

谷歌 Chrome 最新版升级:更强的安全检查功能守护你的上网安全
谷歌 Chrome 浏览器产品经理 Andrew Kamau 在最新发布的博文中宣布,Chrome 浏览器迎来了新一轮的安全升级。新版 Chrome 在后台自动运行安全检查功能,采取了额外的主动措施来保障用户的安全。 自动撤销通知权限 新版 Chrome 浏览器采用了一项基于谷歌安…...

深度学习自编码器 - 收缩自编码器(CAE)篇
序言 在深度学习的浪潮中,收缩自编码器( Compressive Autoencoder, CAE \text{Compressive Autoencoder, CAE} Compressive Autoencoder, CAE)作为自编码器的一种高级形式,正逐步崭露头角。收缩自编码器在保留自编码器核心功能—…...

Dubbo与SpringCloud的区别和优缺点
经常会有同学问我,Dubbo和SpringCloud的选择。甚至也经常会有面试官就这个问题刨根问底。 说实话,其实我不太喜欢回答这个问题,本质上来讲,Dubbo的SpringCloud可以算是完全不同赛道的两种东西,就好像问大家西瓜和土豆我…...