函数式编程更优雅实现to-do list
itomcoil 2025-01-11 13:58 19 浏览
本文为Electron+Vue3开发桌面端软件系列文章第三篇
通过本文,你将获得:
1. 函数式编程关键概念
2. 如何使用函数式编程
我开发的今日清单(下载地址:https://github.com/Zuowendong/my-todolist/releases/tag/v1.0.0)这款软件,如果剔除了Electron带来的功能,其核心业务TodoList组件实现就是通过Vue3完成的。而函数式编程属于JS高级范畴知识,也是高级前端面试绕不过去的考点。有道是,知其然,知其所以然。
函数式编程
JS函数式编程是一种编程范式,强调的是使用纯函数和不可变数据来构建应用程序。其核心思想是把计算作为函数应用的连续组合,而不是通过改变状态和执行可变的命令式代码。
在函数式编程中,函数被视为一等公民,可以作为参数传递给其他函数,也可以作为返回值返回。其目标是通过组合和转换函数来构建程序,而不是通过修改共享的状态。
函数式编程通常涉及到的一些关键概念:
- 纯函数:在给定相同的输入时,总是产生相同的输出,并且没有副作用的函数。纯函数不会改变传入的参数或外部状态,它只依赖于输入并返回一个新的输出。
- 副作用:函数或表达式执行过程中对除了返回值之外的程序状态进行的可观察改变。其包括但不限于以下情况:
- 修改外部变量或状态:例如全局变量、类的属性或模块的状态。 I/O 操作:函数可能会执行输入输出操作,例如读写文件、发送网络请求、修改数据库等。 引发异常:函数可能会引发异常,导致程序的正常流程被中断。 控制流程的改变:例如使用跳转语句(break、continue、return)或抛出异常来改变程序的执行路径。
- 不可变数据:数据一旦创建后就不能被修改。不可变数据有助于减少副作用和意外的数据更改,并且可以简化并行处理和状态管理。
- 函数组合:把函数链接在一起,将一个函数的输出作为另一个函数的输入。
- 高阶函数:接受一个或多个函数作为参数,或者返回一个函数作为结果的函数。
纯函数
纯函数示例:计算两个数字的和
function add(a, b) {
return a + b;
}
// 调用纯函数
const result = add(3, 5);
console.log(result); // 输出 8
上面例子中add函数符合纯函数特点。无论何时调用 add(3, 5),它都会返回 8,并且不会产生任何副作用。
不可变数据
不可变数据示例:添加新元素到数组
function addElement(arr, element) {
// 使用扩展运算符创建一个新的数组,将原始数组和新元素连接起来
return [...arr, element];
}
// 创建一个原始数组
const originalArray = [1, 2, 3];
// 调用函数添加新元素到数组,得到一个新的数组
const newArray = addElement(originalArray, 4);
console.log(originalArray); // 输出 [1, 2, 3]
console.log(newArray); // 输出 [1, 2, 3, 4]
在上面例子中,addElement函数接受一个数组 arr 和一个新元素 element。它通过使用扩展运算符 ... 创建一个新数组,将原始数组和新元素连接起来,返回一个新的数组。原始数组保持不变,而函数返回一个新的数组,没有改变原始数组的状态。
这里就使用不可变的数据结构来处理数组。通过创建新的数组,而不是修改原始数组,保持了原始数组的不可变性。
函数组合
以今日清单中文件处理为示例:当获取到设置的目录下所有文件名列表数据后,过滤出txt文件,处理回显数据列表,按照时间由近到远排序。
需要逐步完成筛选,信息重组,排序三块逻辑。
const fileNames = ['2023-10-14.txt','2023-10-15.txt','2023-10-16.txt','2023-10-17.txt']
const filterTxt = file => file.split('.')[1] === 'txt'
const fileInfo = (file) => {
return {
name: file,
code: dayjs(file.split('.')[0]).valueOf()
}
}
const fileSort = (a, b) => b.code - a.code
const list = fileNames.filter(filterTxt).map(fileInfo).sort(fileSort)
console.log(list)
上面例子用到了dayjs()这个库,将时间转成时间戳方便排序使用,可以在dayjs官网的控制台中测试该示例,dayjs官网中已经将dayjs方法挂载在window上。
高阶函数
高阶函数示例:封装日志函数,在结果输出的前后打印日志。
function withLogging(fn) {
return function (...args) {
console.log('Before function call');
const result = fn(...args);
console.log('After function call');
return result;
};
}
function greet(name) {
console.log(`Hello, ${name}!`);
}
const wrappedGreet = withLogging(greet);
wrappedGreet('John');
输出结果如下:
高阶函数可以用于实现许多有用的编程模式和技术,例如:
- 回调函数:将一个函数作为参数传递给另一个函数,在适当的时候调用该函数,用于实现异步操作和事件处理等场景。
- 函数组合:将多个函数组合在一起,形成一个新的函数,以实现复杂的逻辑或数据转换。
- 函数柯里化:通过固定部分参数创建一个新的函数,用于生成具有特定功能的函数。
- 迭代和遍历:使用高阶函数(如map、filter、reduce等)对数组操作和转换,避免显式的循环结构。
- 函数装饰器:通过包装现有函数,添加额外的功能或修改其行为。
这里具体介绍一下函数柯里化。
函数柯里化的特点是每次只接受一个参数,并返回一个新的函数,等待下一个参数的传入。
一个常见的例子是创建可复用的函数模板。例如有一个简单的加法函数:
function add(a, b) {
return a + b;
}
假设在多个地方需要使用相同的加数,例如 2。使用函数柯里化就可以创建一个新的函数,将 2 作为固定的参数,从而创建一个可复用的加法函数。
function curry(fn, ...fixedArgs) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...fixedArgs, ...args);
} else {
return curried(...args);
}
};
}
const add2 = curry(add, 2);
console.log(add2(3)); // 输出 5
console.log(add2(5)); // 输出 7
在上面示例中,curry函数将 add 函数进行柯里化,并传递 2 作为固定参数。这样就得到了一个新的函数 add2,它只接受一个参数,并将其与固定值 2 相加。现在便可以重复使用 add2,每次传递不同的参数,而无需再次传递 2。
函数柯里化的优势在于它提供了更大的灵活性和可复用性。通过将函数的部分参数固定下来,可以创建更具特定性的函数,并在需要时重复使用它们。这样可以减少代码重复,提高代码的可读性和可维护性。
TodoList 组件
<template>
<div>
<h1>Todo List</h1>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
<button @click="deleteTodo(todo.id)">Delete</button>
</li>
</ul>
<input v-model="newTodoText" type="text" placeholder="Enter a new todo" />
<button @click="addTodo">Add Todo</button>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
import { useTodoStore } from '../stores/todoStore'
export default defineComponent({
setup() {
const todoStore = useTodoStore()
const todos = todoStore.todos
const newTodoText = ref('')
const addTodo = () => {
if (newTodoText.value.trim() !== '') {
const newTodo = {
id: Date.now(),
text: newTodoText.value.trim()
}
todoStore.addTodo(newTodo)
newTodoText.value = ''
}
}
const deleteTodo = (id) => {
todoStore.deleteTodo(id)
}
return {
todos,
newTodoText,
addTodo,
deleteTodo
}
}
})
</script>
在上面的代码中,addTodo函数和 deleteTodo函数通过调用 pinia来处理新增和删除 Todo 的操作。这样可以保持状态的不可变性,遵循函数式编程的原则。
再给出todoStore中处理todos的相关代码:
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useTodoStore = defineStore('todo', () => {
const todos = ref([])
function addTodo(todo) {
todos.value.push(todo)
}
function deleteTodo(id) {
const index = todos.value.findIndex((todo) => todo.id === id)
todos.value.splice(index, 1)
}
return {
todos,
addTodo,
deleteTodo
}
})
虽然函数式编程在TodoList中的应用程度有限,但可以使用 pinia 的状态管理机制结合纯函数和不可变数据的概念,来实现更复杂的状态管理和逻辑处理。这样可以使代码更易于维护、测试和扩展。
也正因如此,因为待办清单这样的业务场景太简单以致不能表现出函数式编程的魅力和便捷。今日清单的源码实现中,并没有采用函数式编程来实现TodoList。个人觉得没有必要为了使用而使用,在实际工作开发中最终的目的是为了更好地解决问题。这里只是通过TodoList组件这样落地的实践更加贴合实际开发来感受函数式编程。
相关推荐
- 最强聚类模型,层次聚类 !!_层次聚类的优缺点
-
哈喽,我是小白~咱们今天聊聊层次聚类,这种聚类方法在后面的使用,也是非常频繁的~首先,聚类很好理解,聚类(Clustering)就是把一堆“东西”自动分组。这些“东西”可以是人、...
- python决策树用于分类和回归问题实际应用案例
-
决策树(DecisionTrees)通过树状结构进行决策,在每个节点上根据特征进行分支。用于分类和回归问题。实际应用案例:预测一个顾客是否会流失。决策树是一种基于树状结构的机器学习算法,用于解决分类...
- Python教程(四十五):推荐系统-个性化推荐算法
-
今日目标o理解推荐系统的基本概念和类型o掌握协同过滤算法(用户和物品)o学会基于内容的推荐方法o了解矩阵分解和深度学习推荐o掌握推荐系统评估和优化技术推荐系统概述推荐系统是信息过滤系统,用于...
- 简单学Python——NumPy库7——排序和去重
-
NumPy数组排序主要用sort方法,sort方法只能将数值按升充排列(可以用[::-1]的切片方式实现降序排序),并且不改变原数组。例如:importnumpyasnpa=np.array(...
- PyTorch实战:TorchVision目标检测模型微调完
-
PyTorch实战:TorchVision目标检测模型微调完整教程一、什么是微调(Finetuning)?微调(Finetuning)是指在已经预训练好的模型基础上,使用自己的数据对模型进行进一步训练...
- C4.5算法解释_简述c4.5算法的基本思想
-
C4.5算法是ID3算法的改进版,它在特征选择上采用了信息增益比来解决ID3算法对取值较多的特征有偏好的问题。C4.5算法也是一种用于决策树构建的算法,它同样基于信息熵的概念。C4.5算法的步骤如下:...
- Python中的数据聚类及可视化分析实践
-
探索如何通过聚类分析揭露糖尿病预测数据集的特征!我们将运用Python的强力工具,深入挖掘数据,以直观的可视化揭示不同特征间的关系。一同探索聚类分析在糖尿病预测中的实践!所有这些可视化都可以通过数据操...
- 用Python来统计大乐透号码的概率分布
-
用Python来统计大乐透号码的概率分布,可以按照以下步骤进行:导入所需的库:使用Python中的numpy库生成数字序列,使用matplotlib库生成概率分布图。读取大乐透历史数据:从网络上找到大...
- python:支持向量机监督学习算法用于二分类和多分类问题示例
-
监督学习-支持向量机(SVM)支持向量机(SupportVectorMachine,简称SVM)是一种常用的监督学习算法,用于解决分类和回归问题。SVM的目标是找到一个最优的超平面,将不同类别的...
- 25个例子学会Pandas Groupby 操作
-
groupby是Pandas在数据分析中最常用的函数之一。它用于根据给定列中的不同值对数据点(即行)进行分组,分组后的数据可以计算生成组的聚合值。如果我们有一个包含汽车品牌和价格信息的数据集,那么可以...
- 数据挖掘流程_数据挖掘流程主要有哪些步骤
-
数据挖掘流程1.了解需求,确认目标说一下几点思考方法:做什么?目的是什么?目标是什么?为什么要做?有什么价值和意义?如何去做?完整解决方案是什么?2.获取数据pandas读取数据pd.read.c...
- 使用Python寻找图像最常见的颜色_python 以图找图
-
如果我们知道图像或对象最常见的是哪种颜色,那么可以解决图像处理中的几个用例,例如在农业领域,我们可能需要确定水果的成熟度。我们可以简单地检查一下水果的颜色是否在预定的范围内,看看它是成熟的,腐烂的,还...
- 财务预算分析全网最佳实践:从每月分析到每天分析
-
原文链接如下:「链接」掌握本文的方法,你就掌握了企业预算精细化分析的能力,全网首发。数据模拟稍微有点问题,不要在意数据细节,先看下最终效果。在编制财务预算或业务预算的过程中,通常预算的所有数据都是按月...
- 常用数据工具去重方法_数据去重公式
-
在数据处理中,去除重复数据是确保数据质量和分析准确性的关键步骤。特别是在处理多列数据时,保留唯一值组合能够有效清理数据集,避免冗余信息对分析结果的干扰。不同的工具和编程语言提供了多种方法来实现多列去重...
- Python教程(四十):PyTorch深度学习-动态计算图
-
今日目标o理解PyTorch的基本概念和动态计算图o掌握PyTorch张量操作和自动求导o学会构建神经网络模型o了解PyTorch的高级特性o掌握模型训练和部署PyTorch概述PyTorc...
- 一周热门
- 最近发表
- 标签列表
-
- ps图案在哪里 (33)
- super().__init__ (33)
- python 获取日期 (34)
- 0xa (36)
- super().__init__()详解 (33)
- python安装包在哪里找 (33)
- linux查看python版本信息 (35)
- python怎么改成中文 (35)
- php文件怎么在浏览器运行 (33)
- eval在python中的意思 (33)
- python安装opencv库 (35)
- python div (34)
- sticky css (33)
- python中random.randint()函数 (34)
- python去掉字符串中的指定字符 (33)
- python入门经典100题 (34)
- anaconda安装路径 (34)
- yield和return的区别 (33)
- 1到10的阶乘之和是多少 (35)
- python安装sklearn库 (33)
- dom和bom区别 (33)
- js 替换指定位置的字符 (33)
- python判断元素是否存在 (33)
- sorted key (33)
- shutil.copy() (33)