浅析函数式编程与前端

简介: 浅析函数式编程与前端

什么是函数式编程


我们常见的编程范式有两种:命令式和声明式,比如我们熟悉的面向对象思想就属于命令式,而函数式编程属于声明式。而且顺带说一句,函数式编程里面提到的“函数”不是我们理解的编程中的“function”概念,而是数学中的函数,即变量之间的映射。

那么,函数式编程和我们熟知的声明式编程区别是什么?引用知乎用户的一句话来做总结:函数式编程关心数据的映射,命令式编程关心解决问题的步骤

举个例子,如果编写一个函数来实现把数组的每个数字都变成它本身的2倍,命令式编程的思路应该是:遍历一次数组,并且把每个数字乘以2,代码如下:

const solution = (arr) => {
  const newArr = [];
  for(let i = 0;i < arr.length;i++) {
    newArr.push(arr[i]*2);
  }
  return newArr;
}
复制代码

但是如果从函数式编程的思维去思考,无非就是数组A的每个元素是数组B每个元素的两倍,存在一个映射:[a, b, c, d, ...] => [2a, 2b, 2c, 2d, ...],代码如下:

const solution = (arr) => {
  return arr.map(item => {
    return item*2;
  })
}
复制代码

从上面这两个简单的例子可以看出来,函数式编程与命令式编程的思路最大的不同在于:函数式更关心数据的映射。

在前端开发领域中,有很多函数式的使用,比如React框架,它本身的设计理念就是View = Fn(Data),而且还有函数式组件以及高阶组件等等,无一不都透露着对函数式编程的实践。


纯函数


纯函数是函数式编程中一个很重要的概念,它的定义是:纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,且不依赖外部环境。

比如说对于数组的方法slice和splice来说(例子来自于函数式编程指北):

var xs = [1,2,3,4,5];
// 纯的
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]
// 不纯的
xs.splice(0,3);
//=> [1,2,3]
xs.splice(0,3);
//=> [4,5]
xs.splice(0,3);
//=> []
复制代码

可以从例子里看出,对于slice来说,它对于相同的输入总能返回相同的输出;而splice直接在原数组上作出改变,产生了可观察到的副作用,即改变了数组。

再来一个例子(例子来自函数式编程指北):

// 不纯的
var minimum = 21;
var checkAge = function(age) {
  return age >= minimum;
};
// 纯的
var checkAge = function(age) {
  var minimum = 21;
  return age >= minimum;
};
复制代码

很明显,在不纯的版本中,checkAge的返回结果直接依赖于外部的变量minimum,这样子就做不到对于相同的输入总是返回相同的输出了,因为只要外部环境的变量一变,直接影响到函数的输出。


副作用


前面一直在说副作用,到底什么是副作用?哪些影响可以被算作是副作用?

副作用的定义是:副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。

副作用包含但不限于:

  • 更改文件系统
  • 往数据库插入记录
  • 发送一个 http 请求
  • 可变数据
  • 打印/log
  • 获取用户输入
  • DOM 查询
  • 访问系统状态

可以看出来,只要是跟外部环境发生了交互的行为都会带来副作用,与外部环境交互了就可能会影响到函数的输出,函数式编程之所以这么在乎副作用,就是因为函数式编程的哲学就是假定副作用是造成不正当行为的主要原因。


优势


纯函数因为它的纯和没有副作用,可以带来以下几个好处


可预测性


因为纯函数没有副作用,对于同一个输入来说,我们可以预测到它的输出,这对于前端常见的共享状态以及修改很有用。

再看看Redux,在它的官方文档里面提到:

To specify how the state tree is transformed by actions, you write pure reducers. Reducers are just pure functions that take the previous state and an action, and return the next > state. Remember to return new state objects, instead of mutating the previous state.

很明显,Redux要求你使用纯函数的方式来写reducer,可预测、无副作用的纯函数正中下怀。


可缓存性


为对于纯函数,固定的输入可以得到固定的输出,所以可利用这一点来做缓存。

例如(例子来自函数式编程指北):

var memoize = function(f) {
  var cache = {};
  return function() {
    var arg_str = JSON.stringify(arguments);
    cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
    return cache[arg_str];
  };
};
var squareNumber  = memoize(function(x){ return x*x; });
squareNumber(4);
//=> 16
squareNumber(4); // 从缓存中读取输入值为 4 的结果
//=> 16
squareNumber(5);
//=> 25
squareNumber(5); // 从缓存中读取输入值为 5 的结果
//=> 25
复制代码


柯里化


柯里化(curry)的理念很简单:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

举个例子(例子来自函数式编程指北):

var add = function(x) {
  return function(y) {
    return x + y;
  };
};
var increment = add(1);
var addTen = add(10);
increment(2);
// 3
addTen(2);
// 12
复制代码

上面这个例子定义了一个add函数,它接受参数x并且返回一个函数,调用add函数之后,返回的函数就以闭包的形式记住了第一个参数x。

柯里化在日常编程中的使用主要体现在“预加载函数”,即提前缓存一部分参数,以便在未来使用。例如在vue源码中,就有一个柯里化的经典使用:

export function createPatchFunction (backend) {
  let i, j
  const cbs = {}
  const { modules, nodeOps } = backend
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    //...
  }
}
复制代码

因为vue需要支持web和weex,而且patch操作里面的逻辑在不同的平台上基本相同,只是把虚拟DOM映射到真实元素的操作方法不一样,所以提前把相关的API传入createPatchFunction,返回一个固化好平台操作元素API的patch方法,以后使用的时候只需要传入oldVnode和vnode等等参数就好了,这种利用柯里化的技巧值得学习。


组合


考虑一下存在如下的方法:

var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = function(x){
  return exclaim(toUpperCase(x)); // 不优雅
};
shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"
复制代码

这种方法里面套方法的做法,看起来略难受且不优雅,如果使用组合的方式,可以修改成下面这样:

var shout = compose(exclaim, toUpperCase);
shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"
复制代码

组合式的写法可以让我们在开发中用拼装来代替直接封装,使代码更加优雅,比如Redux中的compose方法:

Composes functions from right to left.

This is a functional programming utility, and is included in Redux as a convenience. You might want to use it to apply several store enhancers in a row.

Arguments

(arguments): The functions to compose. Each function is expected to accept a single parameter. Its return value will be provided as an argument to the function standing to the left, and so on. The exception is the right-most argument which can accept multiple parameters, as it will provide the signature for the resulting composed function.

Returns

(Function): The final function obtained by composing the given functions from right to left.

import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import DevTools from './containers/DevTools'
import reducer from '../reducers'
const store = createStore(
  reducer,
  compose(applyMiddleware(thunk), DevTools.instrument())
)
复制代码


没有银弹


本文只是对函数式最基本的一些特性的学习和介绍,以及在前端开发的实践。如果想深入学习函数式编程,比如Monad之类的,可以看一下参考链接,更上一层楼。

软件开发领域没有银弹,没有一种编程范式或者框架放之四海而皆准。对于函数式编程也是如此,取其精华去其糟粕,防止拿着锤子看什么都像是钉子。


参考

函数式编程指南

编程的宗派

什么是函数式编程思维?

前端开发js函数式编程真实用途体现在哪里?

有哪些函数式编程在前端的实践经验?

JavaScript函数式编程

相关文章
|
SQL 存储 关系型数据库
PolarDB 开源基础教程系列 4 日常运维
PolarDB日常运维指南涵盖了多个关键操作,包括读写节点故障切换、增加只读节点、配置WAL日志归档、备份与恢复、创建容灾实例以及排查CPU负载高等。通过详细的步骤和代码示例,本文档帮助用户在本地环境中体验和学习PolarDB的高级功能,如共享存储架构下的集群管理。特别地,文档提供了如何使用`polar_basebackup`工具进行备份和恢复,确保数据安全;并通过`pg_stat_statements`插件定位慢查询,优化数据库性能。此外,还介绍了常见问题的排查方法,如业务量上涨或长时间执行的SQL语句导致的CPU高负载。更多内容和进阶课程可参考提供的GitHub链接和官方文档。
995 2
|
人工智能 自然语言处理 搜索推荐
对话批改邦 | 从0到30万用户,如何抓住AI教育增长机遇
阿里云【AI访谈录】本期邀请AI创业者、批改邦创始人王庆棒,分享其团队如何通过AI技术打造教育场景下的教学助手。批改邦以作文批改切入教育行业,上线一年用户突破30万,付费转化率超30%。王庆棒围绕AI如何标准化主观评价、大模型在教育中的落地趋势、未来“杀手级AI应用”的形态等话题,分享一线实战经验。
1183 0
|
JSON 监控 小程序
微信百度字节小程序包过大解决方案(实战经验总结)-优雅草卓伊凡|果果|小无
微信百度字节小程序包过大解决方案(实战经验总结)-优雅草卓伊凡|果果|小无
1883 14
微信百度字节小程序包过大解决方案(实战经验总结)-优雅草卓伊凡|果果|小无
|
存储 NoSQL Java
Tablestore集成MCP协议: 标量与向量混合检索的新范式
基于表格存储(Tablestore)实现的MCP(Model Context Protocol)服务,支持文档存储与混合检索工具两大功能。通过Cherry-Studio界面和通义千问qwen-max模型进行演示,展示了文本数据上传、向量嵌入及查询过程。此外,详细说明了Python和Java版本的本地运行步骤、环境配置及二次开发方法,并提供了集成三方工具如Cherry Studio的应用示例。Tablestore凭借混合查询、Serverless低成本、弹性扩展等优势,为MCP场景提供高效解决方案。
1343 3
|
JSON 前端开发 API
快递单号生成器在线, 快递单号模拟生成器, 虚拟物流信息在线生成【python】
项目包含三个主要模块:快递单号生成器核心逻辑、Flask Web应用程序和前端HTML页面
|
人工智能 小程序 数据处理
uni-app开发AI康复锻炼小程序,帮助肢体受伤患者康复!
近期,多家康复机构咨询AI运动识别插件是否适用于肢力运动受限患者的康复锻炼。本文介绍该插件在康复锻炼中的应用场景,包括康复运动指导、运动记录、恢复程度记录及过程监测。插件集成了人体检测、姿态识别等功能,支持微信小程序平台,使用便捷,安全可靠,帮助康复治疗更加高效精准。
|
云安全 运维 安全
阿里云免费版SSL证书申请及部署图文教程指导
SSL证书是个人和企业搭建网站不可或缺的云安全产品,SSL证书能够为网站和移动应用(APP)及小程序提供数据HTTPS加密协议访问,保障数据的安全。阿里云SSL证书有收费版的也有免费版的,有的新手用户由于是初次在阿里云申请SSL证书,可能不是很清楚免费版证书的申请和部署流程,本文为以图文形式为大家展示阿里云免费版SSL证书最新的申请及部署教程,以供参考。
|
机器学习/深度学习 PyTorch 调度
内部干货 | 基于华为昇腾910B算力卡的大模型部署和调优-课程讲义
近日上海,TsingtaoAI为某央企智算中心交付华为昇腾910B算力卡的大模型部署和调优课程。课程深入讲解如何在昇腾NPU上高效地训练、调优和部署PyTorch与Transformer模型,并结合实际应用场景,探索如何优化和迁移模型至昇腾NPU平台。课程涵盖从模型预训练、微调、推理与评估,到性能对比、算子适配、模型调优等一系列关键技术,帮助学员深入理解昇腾NPU的优势及其与主流深度学习框架(如PyTorch、Deepspeed、MindSpore)的结合应用。
6673 13
|
Linux
CentOS 7.x时间同步服务chrony配置详解
文章详细介绍了在CentOS 7.x系统中如何安装和配置chrony服务,以及它与ntpd服务的对比,强调了chrony在时间同步方面的高效性和准确性。
1916 2
CentOS 7.x时间同步服务chrony配置详解
|
安全 Java 应用服务中间件
当遇到非法 URL 参数时,如何保障网页正常打开
访问如`http://example.com?a@b=1`的链接出现400 Bad Request错误,这是因为Tomcat不允许请求目标中含有非法字符。Spring Boot 2可通过配置`server.tomcat.relaxed-query-chars`来允许特殊字符,但这样做可能引入安全风险。因此,建议在Nginx层使用`rewrite_by_lua_block`和`ngx.redirect`进行重定向,将非法字符替换为合法形式,如`http://example.com?ab=1`,同时记录日志以监控。此方案能避免直接修改后端代码,提高安全性。
1170 0

热门文章

最新文章