JS函数式编程 - 概念
最近在看Typescript,顺便看了一些函数式编程,然后半个国庆假期就没有了。做个笔记,分几个部分写吧。
最开始接触函数式编程的时候,第一个接触的概念就是高阶函数,和柯里化。咋一看,这不就是长期用来讲作用域的demo吗?我在日常也有用啊,有啥吗?
其实呢,设计模式或则编程范式往往不在于技巧,而在于思想。函数式编程就是一种编程的范式,并不在于技巧多么叼,而在于它的思想。其次才是由设计思想才衍生出来的技巧,技巧往往而言是服务于思想的。所以我觉得最开始学习函数式编程最好先了解一些相关概念和思想会比较好。
函数是一等公民(first class)
如果理解直接看为一等公民的好处好处。
其实说函数式一等公民的意思就是说函数和其他“公民”具有相同的属性。就像任何一种数据类型,它能够被存储在数组中,能够作为函数的参数,能够赋值给变量:
const hello = (name) => (`Hello ${name}!`)
const sayHello = hello; // 作为变量
const helloArray = [hello, sayHello]
上面的代码没有什么意义,只是表达函数在JavaScript中是一等公民,和一个值一样。
函数是一等公民的好处
拿一个callback的例子来讲,比如你用fetch发个请求时:
fetch('getPostLink')
.then(res => renderPosts(res))
.catch(err => handleError(error))
上面其实可以直接传递一个函数作为回调,加一层包裹其实没有必要:
fetch('getPostLink').then(renderPosts).catch(handleError)
多一层函数的包裹并没有任何意义,完全是多余的代码。再看一个例子:
const postController = {
find(postId) { return Db.find(postId) },
delete(postId) { return Db.delete(postId) },
...
}
上面的代码其实就是聚合一些功能作为一个对象,但是多加了一层的函数,也是没有必要的,在阅读的时候到会增加复杂度,其实postController.find === Db.find
,所以完全没有再去包裹一层函数:
const postController = {
find: Db.find,
delete: Db.delete,
...
}
上面的代码是不是更表意,然而如果js的函数不能像值一样传递,上面的简写都是不可能的。上面的代码其实还有一个好处,你不用去纠结如何命名在两层函数之间的参数了。这种风格代码是符合Pointfree的,我们后面要介绍。另外,函数式编程是操作函数的,所以函数是一等公民也是函数式的基石,基本上如果js不支持这一项,函数式根本玩不转。
纯函数
让我举一个例子,大家在小学多学过一元一次方程吧:
f(x) = ax+b
这就是一个纯函数,一个输入然后返回一个输出。所有的东西都是围绕输入的,一个输入只可能返回一个输出,然后对任何其他没在作用域中的变量没有任何操作。
更书面的解释:一个纯函数一个输入永远都只有一个同样的输出,然后不会产生任何副作用。副作用是啥我们一会儿再说。
不纯的函数
通常不纯的函数分为两类,一种是会改变输入的:
const numbers = [1, 2, 3]
// 纯函数
numbers.slice(0, 3) // [1, 2, 3]
numbers.slice(1, 3) // [2, 3]
numbers.slice(0, 2) // [1, 2]
// 不纯的函数
numbers.splice(0, 3) // [1, 2, 3]
numbers.splice(0, 3) // []
上面中在numbers这个数组上面的两个方法,slice
是纯函数。而splice则不是纯函数,它会改变输入的数值。做了额外的事。
另外一种是对函数以外的状态有依赖的:
let endpointForYoung = 18
// 不纯的函数
const checkYoungPeople = age => age <= endpointForYoung
// 纯函数
const checkYoungPeople = age => {
const endpointForYoung = 18
return age <= endpointForYoung
}
像上面的函数,第一个就是不纯的,他依赖的作用域之外的一个变量,一旦这个变量改变,这个函数返回的值就会跟着改变。
副作用
副作用就是在函数计算过程中,对函数外的状态进行更改或则与函数外状态进行交互的行为。首先副作用会导致函数不纯,是程序有不可控的依赖,不便于管理。但是,副作用是不可消除的,在正常的编程活动中是必然伴随着副作用的。所以在面对副作用时,问题不是如何消除副作用,而是如何管理副作用。这个,会在我们讲解范畴论相关概念的时候再深入。
正常编程活动中会引入的副作用有这些:
- 文件读写操作
- 增删改查数据库
- http 请求
- 打印log
- 获得用户输入
- 获取dom元素
当然不限于上面这几种,还有很多行为都带有副作用。
纯函数的好处
- 能缓存
纯函数的每次输入和输出都是没有状态的,所以结果都一样,能够被缓存在任何地方而不会造成错误。
- 可移植性,自文档
// 不纯函数
const signUp = (attrs) => {
const user = saveUser(attrs);
welcomeUser(user);
};
// 纯函数
const signUp = (Db, Email, attrs) => () => {
const user = saveUser(Db, attrs);
welcomeUser(Email, user);
};
第二个signUp
依赖是从上传递的,所以能直观的看出saveUser
需要Db
,welcomeUser
还需要Email
。在不纯的函数中你很难在调用的时候知道他的依赖,你需要查看代码,才能搞清楚,“哦,原来还用了Db存了波数据啊。”
依赖作为参数传入,也很容易的在移植到其他场景使用,毕竟函数只是功能,针对不同场景操作不同的数据。
- 易于测试
写单测的时候,最麻烦的就是如何mock数据。通常有两类数据最难mock,第一个是全局变量,比如document
,另外一类是import
进来的依赖,对于这两种,虽然在一些测试套件中有现成的工具库去mock。但是,都是很诡异的方式。
而如果是函数式的话,你测的就是一个输入一个输出,没有外部的影响,是非常容易测试的。
- 并行代码
纯函数都是没有状态的,那即使跑在多台机器多个进程,每个单元相互之间是没有耦合关系的。
Pointfree
大家可以看阮老师的这篇博客了解一下:http://www.ruanyifeng.com/blo...
我只扯一下Pointfree风格代码的好处:
- 正如上面所说的中间变量没有意义,不需要给变量命名
- 代码更简洁,精炼,不用将过多的状态暴露给消费者
当然也有人认为其将太多的状态隐藏了,初读代码很难理解,只有看了具体函数实现功能才能知道真正的意图,对于代码的可读性而言,很糟糕。
这里有一处在hacker news上的讨论Point-Free style: What is it good for?。另外这还有一篇具体使用场景的文章https://medium.freecodecamp.org/how-point-free-composition-will-make-you-a-better-functional-programmer-33dcb910303a。有兴趣的小伙伴可以自己看看。
添加一张我笔记未整理的脑图。
OK,下一篇介绍一下函数组合和柯里化。
来源:https://segmentfault.com/a/1190000016629638
JS函数式编程 - 概念的更多相关文章
- js函数式编程术语总结 - 持续更新
参考文档1 参考文档2 函数式编程术语 高阶函数 Higher-Order Functions 以函数为参数的函数 返回一个函数的函数 函数的元 Arity 比如,一个带有两个参数的函数被称为二元函数 ...
- js函数式编程
最近在看朴灵的<深入浅出nodejs>其中讲到函数式编程.理解记录下 高阶函数 比较常见,即将函数作为参数,或是将函数作为返回值得函数. 如ECMAScript5中提供的一些数组方法 fo ...
- js函数式编程——蹦床函数
概述 这是我在学习函数式编程的时候,关于递归,尾递归,相互递归和蹦床函数的一些心得,记下来供以后开发时参考,相信对其他人也有用. 参考资料:JavaScript玩转Clojure大法之 - Tramp ...
- js函数式编程(二)-柯里化
这节开始讲的例子都使用简单的TS来写,尽量做到和es6差别不大,正文如下 我们在编程中必然需要用到一些变量存储数据,供今后其他地方调用.而函数式编程有一个要领就是最好不要依赖外部变量(当然允许通过参数 ...
- js函数式编程(一)-纯函数
我将写的第一个主题是js的函数式编程,这一系列都是mostly adequate guide这本书的读书总结.原书在gitbook上,有中文版.由于原作者性格活泼,书中夹杂很多俚语,并且行文洒脱.中文 ...
- java1.8函数式编程概念
有关函数式编程 ·1 函数作为一等公民 特点:将函数作为参数传递给另外一个函数:函数可以作为另外一个函数的返回值 ·2 无副作用 函数的副作用指的是函数在调用过程中,除了给出了返回值外,还修改了函数外 ...
- jdk8 函数式编程概念
yls 2019/11/7 函数式接口 如果一个接口只有一个抽象方法,那么该接口就是函数式接口 如果我们在某接口上声明了FunctionalInterface注解,那么编译器就会按照函数式接口的定义来 ...
- JS函数式编程【译】2.1 函数式编程语言
- JS函数式编程【译】2.2 与函数共舞
随机推荐
- c# asp.net 修改webconfig文件 配置
#region 修改config文件 /// <summary> /// 修改config文件(AppSetting节点) /// </summary> /// <par ...
- 延迟对象deferred
Twisted 官方称,“Twisted is event-based, asynchronous framework ”.这个“异步”功能的代表就是 deferred. deferred 的作用类似 ...
- AppServer获取参数的方法
AppServer中从APP_PARAM表中根据param_code获取param_value: appManageService.getParamValueByCode(param_code) -- ...
- 第六章 Odoo 12开发之模型 - 结构化应用数据
在本系列文章第三篇Odoo 12 开发之创建第一个 Odoo 应用中,我们概览了创建 Odoo 应用所需的所有组件.本文及接下来的一篇我们将深入到组成应用的每一层:模型层.视图层和业务逻辑层. 本文中 ...
- 巧用 position:absolute
1.跟随性 下面这种方法更加简便以及更方便维护, 例如“西部世界”,由于不用将父元素设为position:relative,position:absolute的位置也就不用根据文字多少而重新进行top ...
- 原生微信小程序的生命周期
小程序的生命周期函数:onLaunch:function(){当启动小程序时触发小程序只会启动1次,一般为初次打开时一般只会触发一次},onShow:function(){当小程序从后台切入到前台时触 ...
- 深入protoBuf
ProtoBuf 官方文档翻译 [翻译] ProtoBuf 官方文档(一)- 开发者指南 [翻译] ProtoBuf 官方文档(二)- 语法指引(proto2) [翻译] ProtoBuf 官方文档( ...
- 2019牛客暑期多校赛(第一场) A Equivalent Prefixes(单调栈)
传送门:https://ac.nowcoder.com/acm/contest/881/A 题意:给定两个数组a和b,求最大的p,满足在区间 [1,p] 中任何区间的两个数组的最小值的下标都相等. 思 ...
- JSP四大域对象与九大内置对象
什么是内置对象? 在jsp开发中会频繁使用到一些对象,如 ServletContext.HttpSession.PageContext 等: 如果每次我们在jsp页面中需要使用这些对象都要自己亲自动手 ...
- mysql 对数据库操作的常用sql语句
1.查看创建某个数据库的 创建语句 show create database mysql 这个sql语句的意思是 展示创建名为mysql的数据库的 语句.执行之后如下图所示 仿造上面这个创建语句 创建 ...