前言

上一篇文章介绍了javascript中的compose函数的实现,我是用了递归的思想去让函数依次执行,lodash中是用了迭代的思想依次执行函数,但实现了以后我还是觉得有些别扭,仔细想想,我们实现的是一个函数式编程用到的函数,但是实现的方法还是太命令式了,函数还是命令式的执行,通俗点说,还是太把函数当成函数了,在我的理解中,函数和普通变量没什么区别,只是执行的方法不一样,一旦赋予了函数这个执行的属性,我们就可以完全将函数当成普通变量去对待。

函数和普通变量没什么区别,只是需要偶尔执行一下

实现

1.函数世界的加号

举个例子

1 + 2 = 3
'a' + 'b' = 'ab'
func1 '+' func2 -> func3

前两个例子就是普通变量的操作,最后一个例子是函数的操作,本质上看来,没有任何区别,两个函数作用的结果就是生成一个函数,只不过在函数的世界里,这个加号的意义就是如何变换生成一个新的函数,回到compose来,在compose中,加号的意义就是把一个函数的执行结果当成下一个函数的输入,最后在生成一个函数,就像下面这样

var fn = (func1, func2) => (...args) => func2.call(this, func1.apply(this, args))

在这个例子里面,func1的执行结果就是func2的参数,并且生成了一个新的函数fn,我们给这个fn传递参数,它就会作为func1的参数来启动执行,最后得到了函数依次执行的效果,这就是最简单的compose,这个函数就是ramda.js实现compsoe需要的第一个函数_pipe

var _pipe = (f, g) => (...args) => g.call(this, f.apply(this, args))

_pipe就定义了compose中所谓加号的意义了。

2.'不一样的'reduce

在这里提到了reduce,是不是有一点感觉,reduce的作用就是让一个数组不断的执行下去,所以肯定能和咱们这个compose有点联系,先举个reduce最常用的例子,求数组的和

var a = [1,2,3,4,5]
a.reduce((x, y) => x + y, 0)

这个就是不断的将两个数求和,生成一个新的数,再去和下一个数求和,最后得到15,下面想一下,如果把数字换成函数会怎么样,两个函数结合生成一个新的函数,这个结合法则就使用上面的_pipe,这个新的函数再去结合下一个函数,直到最后一个函数执行完,我们得到的还是函数,我们前面说了,函数知识偶尔需要执行一下,这个函数的生成和执行过程是反向递归的过程。利用这个思想,就可以寥寥几行(甚至只需要一行)就写出来这个非常函数式的compose

var reverse = arr => arr.reverse()
var _pipe = (f, g) => (...args) => g.call(this, f.apply(this, args));
var compose = (...args) => reverse(args).reduce(_pipe, args.shift())

举个例子验证一下,我们把首个函数做多元处理,再upperCase,再repeat

var classyGreeting = (firstName, lastName) => "The name's " + lastName + ", " + firstName + " " + lastName
var toUpper = str => str.toUpperCase()
var repeat = str => str.repeat(2)
var result = compose(repeat, toUpper, classyGreeting)('dong', 'zhe')
// THE NAME'S ZHE, DONG ZHETHE NAME'S ZHE, DONG ZHE

我在这里把函数生成过程分析一下

首先我们用_pipe组合classyGreetingtoUpper

f1 = _pipe(classyGreeting, toUpper)
f1 = (...args) => toUpper.call(this, classyGreeting.apply(this, args))

_pipe继续结合f1, repeat

f2 = _pipe(f1, repeat)
f2 = (...args) => repeat.call(this, f1.apply(this, args))

函数的执行过程就会将参数层层传递到最里面的classyGreeting开始执行,从而完成函数的依次执行。ramda.js自己实现了reduce,不仅支持数组的reduce,还支持多种数据结构的reduce,(兼容性也更好?),下一步来分析是如何自己实现数组的reduce的,可与看出,自己分离出来逻辑之后,函数的执行过程和组合的规则部分将分离的更彻底。

3.自己写一个reduce

reduce接受三个参数,执行函数,初始值,执行队列(可以不止为一个数组),返回一个针对这些参数的reduce处理,这里只写数组部分(_arrayReduce),源码中还包含了关于迭代器的_iterableReduce 等等,而且ramda.js对执行函数也有一层对象封装,扩展了函数的功能

var reduce = (fn, acc, list) => (fn = _xwrap(fn), _arrayReduce(fn, acc, list))

在写_arrayReduce之前,先来看一下函数的对象封装_xwrap

var _xwrap = (function(){function XWrap(fn) {this.f = fn;}XWrap.prototype['@@transducer/init'] = function() {throw new Error('init not implemented on XWrap');};XWrap.prototype['@@transducer/result'] = function(acc) {return acc;};XWrap.prototype['@@transducer/step'] = function(acc, x) {return this.f(acc, x);};return function _xwrap(fn) { return new XWrap(fn); };
})()

其实就是对函数执行状态做了一个分类管理
@@transducer/step 这种状态认为是一种过程状态
@@transducer/result 这种状态被认为是一种结果状态
这种状态管理通过对象也是合情合理的
最后再来完成_arrayReduce,就很简单了,这个函数只是专心一件事情,就是写reduce的过程规则。

var _arrayReduce = (xf, acc, list) => {var idx = 0var len = list.lengthwhile (idx < len) {acc = xf['@@transducer/step'](acc, list[idx]);idx += 1;}return xf['@@transducer/result'](acc);
}

至此,ramda.js简化版的reduce就完成了。

4.其他一些功能

tail用来分离初始值和执行队列的,因为初始函数是多元的(接收多个参数),执行队列都是一元(接收一个参数)的,分离还是有必要的

var tail = arr => arr.slice(1)

reverse改变执行顺序

var reverse = arr => arr.reverse()  

_arity我把源代码贴出来,我也不知道为什么这样做,可能是明确指定参数吧,因为reduce生成的函数是可以接受多个参数的,_arity就是处理这个函数的

var _arity = (n, fn) => {switch (n) {case 0: return function() { return fn.apply(this, arguments); };case 1: return function(a0) { return fn.apply(this, arguments); };case 2: return function(a0, a1) { return fn.apply(this, arguments); };case 3: return function(a0, a1, a2) { return fn.apply(this, arguments); };case 4: return function(a0, a1, a2, a3) { return fn.apply(this, arguments); };case 5: return function(a0, a1, a2, a3, a4) { return fn.apply(this, arguments); };case 6: return function(a0, a1, a2, a3, a4, a5) { return fn.apply(this, arguments); };case 7: return function(a0, a1, a2, a3, a4, a5, a6) { return fn.apply(this, arguments); };case 8: return function(a0, a1, a2, a3, a4, a5, a6, a7) { return fn.apply(this, arguments); };case 9: return function(a0, a1, a2, a3, a4, a5, a6, a7, a8) { return fn.apply(this, arguments); };case 10: return function(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) { return fn.apply(this, arguments); };default: throw new Error('First argument to _arity must be a non-negative integer no greater than ten');}
}

5.整合

最后整合出来两个最终的函数pipecompose

var pipe = (...args) => _arity(args[0].length, reduce(_pipe, args[0], tail(args)))
var remdaCompose = (...args) => pipe.apply(this, reverse(args))

再把上面的demo试一下

console.log(remdaCompose(repeat, toUpper, classyGreeting)('dong', 'zhe'))
// THE NAME'S ZHE, DONG ZHETHE NAME'S ZHE, DONG ZHE

整合的完全版我放到了github里

总结

这篇文章主要分析了ramda.js实现compose的过程,其中分析了如何把函数看成一等公民,如何实现一个reduce等等。可以看出,compose的实现从头到尾都是函数式编程的思想,下一篇文章打算结合社区的一道问答题来介绍一下如何用函数式思想来解决问题。我也是初学函数式,有什么说的不准确的地方希望多多指正。

ramda.js的compose源码解析相关推荐

  1. 迷你 JS 框架 Hyperapp 源码解析

    Hyperapp 是最近热度颇高的一款迷你 JS 框架,其源码不到 400 行,压缩 gzip 后只有 1kB,却具有相当高的完成度,拿来实现简单的 web 应用也不在话下.整体实现上,Hyperap ...

  2. PureMVC(JS版)源码解析(五):SimpleCommand类

    之前我们对PureMVC中涉及到观察者模式的三个基本类(Notification/Observer/Notifier)进行了分析,接下来将对PureMVC源码中的其他类进行分析,首先我们讲解Simpl ...

  3. redux 思考以及源码解析

    1. 基本概念 redux有以下几个基本概念: 1.1. action action: 是一个对象,对一个行为的基本描述 {type:'add',todo } 1.2 action creator 一 ...

  4. js怎么调用wasm_Long.js源码解析

    基于现在市面上到处都是 Vue/React 之类的源码分析文章实在是太多了.(虽然我也写过 Vite的源码解析 所以这次来写点不一样的.由于微信这边用的是 protobuf 来进行 rpc 调用.所以 ...

  5. 【Vue.js源码解析 一】-- 响应式原理

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 课程目标 Vue.js 的静态成员和实例成员初始化过程 首次渲染的过程 数据响应式原理 – 最核心的特性之一 准备工作 ...

  6. JavaScript数字运算必备库——big.js源码解析

    概述 在我们常见的JavaScript数字运算中,小数和大数都是会让我们比较头疼的两个数据类型. 在大数运算中,由于number类型的数字长度限制,我们经常会遇到超出范围的情况.比如在我们传递Long ...

  7. 购物车(js+css+html)源码解析

    购物车源码解析 先取得表格: Js代码 1.var table = document.getElementById("table"); 然后遍历表格的行数进行删除: Js代码 1. ...

  8. usestate中的回调函数_React Hooks 源码解析(3):useState

    React 源码版本: v16.11.0 源码注释笔记: airingursb/react​github.com 在写本文之前,事先阅读了网上了一些文章,关于 Hooks 的源码解析要么过于浅显.要么 ...

  9. koa compose源码阅读

    众所周知,在函数式编程中,compose是将多个函数合并成一个函数(形如: g() + h() => g(h())),koa-compose则是将 koa/koa-router 各个中间件合并执 ...

最新文章

  1. Android的ToolBar
  2. python中类方法与实例方法的区别-Python实例方法、静态方法和类方法详解(包含区别和用法)...
  3. C语言如何产生随机数
  4. ubuntu16.04安装opencv3.4
  5. Go 语言开发第一天,我的学习之路从这里开始
  6. websocket 西部数码php_网页实时聊天之PHP实现websocket
  7. pythonxml库_对python 生成拼接xml报文的示例详解
  8. 设置导航条上的主题一颜色
  9. 浅谈ASP.NET客户端回调
  10. docker 服务器重启 镜像丢失_将你的前端应用打包成docker镜像并部署到服务器?仅需一个脚本搞定...
  11. Ubuntu搜索不到WiFi的解决办法
  12. 一起谈.NET技术,疯狂的想法——基于.NET的软件超市平台构想与5年实现之路
  13. html快闪软件制作,教你如何用PPT轻松完成快闪视频制作?
  14. D1-H哪吒 设置hdmi分辨率
  15. 小程序——添加动画,让图标原地旋转
  16. 四元数和旋转轴及旋转角度之间的转换理解实例
  17. 爱是永恒,依然爱是您
  18. Linux普通用户su root权限的开启和禁止
  19. zabbix 监控下载安装
  20. 95后程序员月薪2万带着电脑送外卖 不想35岁就被社会淘汰 你呢

热门文章

  1. 2018/11/30 快手面试总结
  2. C++_32位和64程序
  3. 药品研发--工艺技术人员积分和职务考核评估管理办法
  4. STK与VC++联合编程实战(第四回:由TLE数据插入卫星对象)
  5. 3.4 以最快的方式完成应用通道的创建
  6. java boxplot_【gloomyfish】数据分析之 – 离群值(Outliers) BoxPlot
  7. 用C语言实现复数和相量的四则运算和相互转化
  8. mklink和junction 详解
  9. c语言实现L1-016 查验身份证
  10. 什么是零代码?零代码与低代码有什么联系与区别?