函数式编程入门实践(一)


Posted in Javascript onApril 20, 2019

在文章之前,先和大家讲一下对于函数式编程(Functional Programming, aka. FP)的理解(下文我会用FP指代函数式编程):

  1. FP需要保证函数都是纯净的,既不依赖外部的状态变量,也不产生副作用。基于此前提下,那么纯函数的组合与调用,在时间顺序上就不会产生依赖,改变多个函数的调用顺序也不必担心产生问题,因此也会消灭许多潜在的bug。
  2. 函数必须有输入输出。如果一个函数缺乏输入或输出,那么它其实是一段处理程序procedure而已。
  3. 函数尽可能的保持功能的单一,如果一个函数做了多件事情,那么它理论上应当被拆分为多个函数。
  4. FP的意义之一就是,在适当的时机使用声明式编程,抽象了程序流的控制与表现,从理解和维护的角度上会胜于命令式编程。
  5. FP是一种范式,但并不意味这和OOP(面向对象编程)冲突,两者当然是可以和谐共存的。个人认为 React 其实就是一个很好的栗子~
  6. Javascript的函数一等公民以及闭包的特性,决定了Javascript的确是适合施展FP的舞台

理解闭包

闭包对于 Javascript 来说,当然十分重要。然而对于函数式编程来说,这更加是必不可少的,必须掌握的概念,闭包的定义如下:

Closure is when a function remembers and accesses variables from outside of its own scope, even when that function is executed in a different scope.

相信大部分同学都对闭包有不错的理解,但是由于对FP的学习十分重要。接下来我还是会??碌拇?蠹夜?槐椤1瞻?褪悄芄欢寥∑渌???诓勘淞康暮??/p>

简单示例如下

// Closure demo
function cube(x) {
 let z = 1;
 return function larger(y) {
 return x * y * z++;
 };
}

const makeCube = cube(10);
console.log(makeCube(5)); // 50
console.log(makeCube(5)); // 100

那么有没有想过在函数makeCube,或者也可以说是函数larger是怎么记住原本不属于自己作用域的变量x和z的呢?在控制台查看makeCube.prototype,点开会发现原来是有个[[Scopes]]这个内置属性里的Closure(cube)记住了函数larger返回时记住的变量x和z。如果多嵌套几层函数,也会发现多几个Closure(name)在[[Scopes]]的Scopes[]数组里,按序查找变量。

函数式编程入门实践(一)

再看下图测试代码:

function cube(x) {
 return function wrapper(y) {
 let z = 1;
 return function larger() {
  return x * y * z++;
 };
 }
}

const makeCubeY = cube(10);
const makeCube = makeCubeY(5);
const $__VAR1__ = '1. This var is just for test.';
let $__VAR2__ = '2. This var is just for test.';
var $__VAR3__ = '3. This var is just for test.';
console.log(makeCubeY.prototype, makeCube.prototype);
console.log(makeCube()); // 50
console.log(makeCube()); // 100

打印makeCubeY.prototype:

函数式编程入门实践(一)

打印makeCube.prototype:

 函数式编程入门实践(一)

通过这几个实验可以从另一个角度去理解Javascript中闭包,一个闭包是怎么去查找不是自己作用域的变量呢?makeCube函数分别从[[Scopes]]中的Closure(wrapper)里找到变量y、z,Closure(cube)里找到变量x。至于全局let、const声明的变量放在了Script里,全局var声明的变量放在了Global里。

在学习FP前,理解闭包是尤为重要的~ 因为事实上大量的FP工具函数都使用了闭包这个特性。

工具函数

unary

const unary = fn => arg => fn(arg);

一元函数,应用于当只想在某个函数上传递一个参数情况下使用。尝试考虑以下场景:

console.log(['1', '2', '3'].map(parseInt)); // [1, NaN, NaN]
console.log(['1', '2', '3'].map(unary(parseInt))); // [1, 2, 3]

parseInt(string, radix)接收两个参数,而map函数中接收的回调函数callback(currentValue[, index[, array]]),第二个参数是index,此时如果parseInt的使用就是错误的。当然除了Array.prototype.map,大量内置的数组方法中的回调函数中都不止传递一个参数,如果存在适用的只需要第一个参数的场景,unary函数就发挥了它的价值,无需修改函数,优雅简洁地就接入了。(对于unary函数,fn就是闭包记忆的变量数据)

identity

const identity = v => v;

有同学会看到identity函数会觉得莫名其妙?是干嘛的?我第一眼看到也很迷惑?但是考虑以下场景:

console.log([false, 1, 2, 0, '5', true].filter( identity )); // [1, 2, "5", true]
console.log([false, 0].some( identity )); // false
console.log([-2, 1, '3'].every( identity )); // true

怎么样?眼前一亮吧,没想到identity函数原来深藏不露,事实上虽然identity返回了原值,但是在这些函数中Javascript会对返回的值进行类型装换,变成了布尔值。比如filter函数。我们可以看MDN定义filter描述如下(看标粗的那一句)。

filter() calls a provided callback function once for each element in an array, and constructs a new array of all the values for which callback returns a value that coerces to true.

constant

const constant = v => () => v;

同样,这个函数...乍一看,也不知道具体有什么用。但是考虑场景如下:

onst p1 = new Promise((resolve, reject) => {
 setTimeout(() => {
 resolve('Hello!');
 }, 200);
});
p1.then(() => 'Hi').then(console.log); // Hi!
p1.then(constant('Hi')).then(console.log); // Hi!
p1.then('Hi').then(console.log); // Hello!

由于Promise.prototype.then只接受函数,如果我仅仅只需要传递一个值时,那么constant便会提供这种便利。当然这个并没有什么功能上的提升,但是的确提高了可阅读性,也是函数式编程的一个优点。

spreadArgs & gatherArgs

const spreadArgs = fn => argsArr => fn( ...argsArr );
const gatherArgs = fn => (...argsArr) => fn( argsArr );

嗯这两个函数见名知义。分别用于展开一个函数的所有参数和收集一个函数所有参数,这两个函数明显对立,那么它们的应用场景又是什么呢?

spreadArgs函数示例如下:

function cube(x, y, z) {
 return x * y * z;
}

function make(fn, points) {
 return fn(points);
}

console.log(make(cube, [3, 4, 5])); // NaN
console.log(make(spreadArgs(cube), [3, 4, 5])); // 60

gatherArgs函数示例如下:

function combineFirstTwo([v1, v2]) {
 return v1 + v2;
}

console.log([1, 2, 3, 4, 5].reduce(combineFirstTwo)); // Uncaught TypeError
console.log([1, 2, 3, 4, 5].reduce(gatherArgs(combineFirstTwo))); // 15

看完以上代码,简单的两个工具函数,轻易的做到了对一个函数的转换,从而使其适用于另一个场景。如果从此应该可以瞥见函数式编程的一点点魅力,那么下面的两个函数将给大家带来更多的惊喜。

partial & curry

const partial = (fn, ...presetArgs) => (...laterArgs) =>
 fn(...presetArgs, ...laterArgs);
 
const curry = (fn, arity = fn.length, nextCurried) =>
 (nextCurried = prevArgs => nextArg => {
 const args = [...prevArgs, nextArg];

 if (args.length >= arity) {
  return fn(...args);
 } else {
  return nextCurried(args);
 }
 })([]);

相信大家对函数柯里化应该或多或少有点了解。维基百科定义:

在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

当然得益于闭包的强大威力,柯里化这个武器得以诞生于Javascript世界。请大家先精读以上关于partiel、curry函数的代码。

喝一杯咖啡~

先模拟一个ajax函数如下:

function ajax(url, params, callback) {
 setTimeout(() => {
 callback(
  `GET ${url} \nparams: ${params} \ndata: Hello! ${params} `
 );
 });
}

考虑partial使用场景如下:

const fetchPerson = partial( ajax, "http://some.api/person" );

fetchPerson('Teddy Bear', console.log);
/*
GET http://some.api/person 
params: Teddy Bear 
data: Hello! Teddy Bear 
*/

考虑curry使用场景如下:

const fetchPerson = curry(ajax)('http://some.api/person');
const fetchUncleBarney = fetchPerson('Uncle Barney');

fetchUncleBarney(console.log);
/*
GET http://some.api/person 
params: Uncle Barney 
data: Hello! Uncle Barney 
*/

partial和curry函数功能相似,但又有具体的不同应用场景,但总体来说curry会比partial更自动化一点。
但是!相信看完示例的同学又会有一连串问号?为什么好好地参数不一次性传入,而非要分开多次传入这么麻烦?原因如下:

  1. 最首要的原因是partial和curry函数都允许我们通过参数控制将一个函数的调用在时间和空间上分开了。传统函数需要一次性将参数凑齐才能调用,但是有时候我们可以提前预置部分参数,在最终需要触发此函数时,才将剩余参数传入。这时候partial和curry就会变得十分有用。
  2. partial和curry的存在让函数组合(compose)会更加便利。(函数组合也计划之后和大家分享,这里就不详细说了)。
  3. 当然最重要是也提升了可阅读性!一开始可能不这么以为,但是如果你实践操作感受之后,也许会改观。

P.S. 关于函数式编程的实践,大家可以使用lodash/fp模块进行入门实践。

一些思考
因为我也是函数式编程的初学者,如有不正确的地方,欢迎大家纠正~

接下来还是会继续整理FP的学习资料,学习实践,连载一些我对于函数式编程的学习与思考,希望和大家一起进步~

谢谢大家(●´∀`●)~

以上所述是小编给大家介绍的Javascript函数式编程详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

 

Javascript 相关文章推荐
ajax请求get与post的区别总结
Nov 04 Javascript
Js实现动态添加删除Table行示例
Apr 14 Javascript
jQuery判断当前点击的是第几个li的代码
Sep 26 Javascript
JavaScript 学习笔记之变量及其作用域
Jan 14 Javascript
BootStrap文件上传样式超好看【持续更新】
May 10 Javascript
windows下vue-cli导入bootstrap样式
Apr 25 Javascript
使用clipboard.js实现复制功能的示例代码
Oct 16 Javascript
「中高级前端面试」JavaScript手写代码无敌秘籍(推荐)
Apr 08 Javascript
Vue extend的基本用法(实例详解)
Dec 09 Javascript
JS正则表达式常见函数与用法小结
Apr 13 Javascript
vue 组件简介
Jul 31 Javascript
Vue实现简单的拖拽效果
Aug 25 Javascript
vue路由对不同界面进行传参及跳转的总结
Apr 20 #Javascript
详解Vue+Element的动态表单,动态表格(后端发送配置,前端动态生成)
Apr 20 #Javascript
详解vue使用$http服务端收不到参数
Apr 19 #Javascript
ajaxfileupload.js实现上传文件功能
Apr 19 #Javascript
AjaxFileUpload.js实现异步上传文件功能
Apr 19 #Javascript
读懂CommonJS的模块加载
Apr 19 #Javascript
js module大战
Apr 19 #Javascript
You might like
深入php中var_dump方法的使用详解
2013/06/24 PHP
PHP网页游戏学习之Xnova(ogame)源码解读(十六)
2014/06/30 PHP
Laravel 4.2 中队列服务(queue)使用感受
2014/10/30 PHP
Windows Server 2008 R2和2012中PHP连接MySQL过慢的解决方法
2016/07/02 PHP
js this函数调用无需再次抓获id,name或标签名
2014/03/03 Javascript
使用text方法获取Html元素文本信息示例
2014/09/01 Javascript
原生js和jquery实现图片轮播淡入淡出效果
2015/04/23 Javascript
老生常谈javascript变量的命名规范和注释
2016/09/29 Javascript
利用JS轻松实现获取表单数据
2016/12/06 Javascript
js 将input框中的输入自动转化成半角大写(税号输入框)
2017/02/16 Javascript
js中删除数组中的某一元素实例(无下标时)
2017/02/28 Javascript
vue深入解析之render function code详解
2017/07/18 Javascript
详解处理Vue单页面应用SEO的另一种思路
2018/11/09 Javascript
vue使用i18n实现国际化的方法详解
2019/09/05 Javascript
基于Vue3.0开发轻量级手机端弹框组件V3Popup的场景分析
2020/12/30 Vue.js
Python配置文件解析模块ConfigParser使用实例
2015/04/13 Python
分享一下Python 开发者节省时间的10个方法
2015/10/02 Python
Python实现计算圆周率π的值到任意位的方法示例
2018/05/08 Python
python实现文件+参数发送request的实例代码
2021/01/05 Python
Python实现我的世界小游戏源代码
2021/03/02 Python
Sunglasses Shop英国:欧洲领先的太阳镜在线供应商之一
2018/09/19 全球购物
Sarenza德国:法国最大的时尚鞋和包包网上商店
2019/06/08 全球购物
Oracle里面常用的数据字典有哪些
2014/02/14 面试题
Linux机考试题
2015/10/16 面试题
Overload和Override的区别
2012/09/02 面试题
预备党员党课思想汇报
2014/01/13 职场文书
《美丽的彩虹》教学反思
2014/02/25 职场文书
法院先进个人事迹材料
2014/05/04 职场文书
施工单位安全责任书
2014/07/24 职场文书
开展批评与自我批评心得体会
2014/10/17 职场文书
个人政风行风自查自纠报告
2014/10/21 职场文书
店长岗位职责
2015/02/11 职场文书
研究生导师推荐信
2015/03/25 职场文书
2015年司法局工作总结
2015/05/22 职场文书
怎样写好工作计划
2019/04/10 职场文书
MySQL系列之十四 MySQL的高可用实现
2021/07/02 MySQL