前言:为什么要学习这个方法

遇到这个方法主要是最近在阅读redux,koa 原理 等多次遇到这个方法,为了更好地理解框架原理,于是深入学习了一下compose的实现。

然后也发现这属于函数式编程的东西,发现函数式编程是进击前端进阶的必经之路,因为像其中的纯函数的概念在redux的reducer中也展示得淋漓尽致​,而保留函数计算结果的思想无论是在vue,还是react等其他框架也多处见到。

所以建议​有时间可以去看下函数试编程。

接下来,就让我们学习下其中的compose函数吧​!

compose简介

compose就是执行一系列的任务(函数),比如有以下任务队列

let tasks = [step1, step2, step3, step4]

每一个step都是一个步骤,按照步骤一步一步的执行到结尾,这就是一个compose

compose在函数式编程中是一个很重要的工具函数,在这里实现的compose有三点说明

  • 第一个函数是多元的(接受多个参数),后面的函数都是单元的(接受一个参数)
  • 执行顺序的自右向左的
  • 所有函数的执行都是同步的

还是用一个例子来说,比如有以下几个函数

let init = (...args) => args.reduce((ele1, ele2) => ele1 + ele2, 0)
let step2 = (val) => val + 2
let step3 = (val) => val + 3
let step4 = (val) => val + 4

这几个函数组成一个任务队列

steps = [step4, step3, step2, init]

使用compose组合这个队列并执行

let composeFunc = compose(...steps)

console.log(composeFunc(1, 2, 3))

执行过程

6 -> 6 + 2 = 8 -> 8 + 3 = 11 -> 11 + 4 = 15

所以流程就是从init自右到左依次执行,下一个任务的参数是上一个任务的返回结果,并且任务都是同步的,这样就能保证任务可以按照有序的方向和有序的时间执行。

compose的实现

好了,我们现在已经知道compose是什么东西了,现在就来实现它吧!

最容易理解的实现方式

思路就是使用递归的过程思想,不断的检测队列中是否还有任务,如果有任务就执行,并把执行结果往后传递,这里是一个局部的思维,无法预知任务何时结束。直观上最容易理解。

const compose = function(...funcs) {
let length = funcs.length
let count = length - 1
let result
return function f1 (...arg1) {
result = funcs[count].apply(this, arg1)
if (count <= 0) {
count = length - 1
return result
}
count--
return f1.call(null, result)
}
}

删繁就简来看下,去掉args1参数

const compose = function(...funcs) {
let length = funcs.length
let count = length - 1
let result
return function f1 () {
result = funcs[count]()
if (count <= 0) {
count = length - 1
return result
}
count--
return f1(result)
}
}

这就好看很多,我们假设有三个方法,aa,bb,cc

 function aa() {
console.log(11);
} function bb() {
console.log(22);
}
function cc() {
console.log(33);
return 33
}

然后传入compose

compose(aa,bb,cc)

此时count = 2,则下面其实是执行cc

result = funcs[count]()

然后count--。再递归执行f1,则下面其实就是执行bb

result = funcs[count]()

这样,就实现了 从funcs数组里从右往左依次拿方法出来调用,再把返回值传递给下一个。

后面的步骤同理​。​

这其实是一种面向过程的思想

手写javascript中reduce方法

为什么要手写?其实你要是能够很熟练的使用reduce,我觉得不必手写reduce,只是我觉得熟悉一下reduce内部的实现可以更好地理解后面的内容,况且 也不会太难呀!

 function reduce(arr, cb, initialValue){
var num = initValue == undefined? num = arr[0]: initValue;
var i = initValue == undefined? 1: 0
for (i; i< arr.length; i++){
num = cb(num,arr[i],i)
}'
return num
}

如代码所示,就是先判断有没有传入初始值,有的话,下面的循环直接 从i = 0开始,否则i=1开始。

如果没有传入初始值,num就取 数组的第一个元素。这也是说明了为什么传入初始值,i就=1,因为第一个都被取出来了,就不能再取一次啦啦啦!

下面使用我们写的reduce方法

 function fn(result, currentValue, index){
return result + currentValue
} var arr = [2,3,4,5]
var b = reduce(arr, fn,10)
var c = reduce(arr, fn)
console.log(b) // 24

好了 ,没毛病,既然我们了解了reduce原理,就看看下面的redux中compose的实现

redux中compose的实现

function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
} if (funcs.length === 1) {
return funcs[0]
}
debugger
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

很简短,非常的巧妙,但是不是很不好理解。不过没关系。

依旧通过例子来讲解。

function aa() {
console.log(11);
} function bb() {
console.log(22);
}
function cc() {
console.log(33);
}

假设只有这三个方法,我们怎样才能先执行cc再执行bb,再执行aa呢?

没错,可以直接写

aa(bb(cc()))

就是这样,非常巧妙,不仅完成了执行顺序,还实现了前一个方法执行返回的结果传递给了下一个即将要执行的方法。

而下面这段代码所做的就是将funcs数组[aa,bb,cc],转化成aa(bb(cc()))

funcs.reduce((a, b) => (...args) => a(b(...args)))

怎么办到的?

看看下面的解释:

reduce内部第一次执行返回的结果是 一个方法

(...args) => aa(bb(...args))

我们现在把这个方法简化成dd,即

dd = (...args) => aa(bb(...args))

reduce内部第二次执行的时候,此时的a 是 上一次返回的dd方法,b是cc

所以执行结果是

(...args) => dd(cc(...args))

dd(cc(...args))不就是先执行cc再执行dd吗?而dd就是执行bb再执行aa。

我的天,!这不是俄罗斯套娃吗!没错 redux中的compose的实现原理就是套娃哈哈哈!

参考文章

https://segmentfault.com/a/1190000011447164

最后

文章首发于公众号《前端阳光》,欢迎加入技术交流群。

前端进阶之认识与手写compose方法的更多相关文章

  1. 2019前端面试系列——JS高频手写代码题

    实现 new 方法 /* * 1.创建一个空对象 * 2.链接到原型 * 3.绑定this值 * 4.返回新对象 */ // 第一种实现 function createNew() { let obj ...

  2. 手写redux方法以及数组reduce方法

    reduce能做什么? 1)求和 2)计算价格 3)合并数据 4)redux的compose方法 这篇文章主要内容是什么? 1)介绍reduce的主要作用 2)手写实现reduce方法 0)了解red ...

  3. UI进阶之--网易彩票手写plist文件,动态创建控制器与tableViewcell

    点击右上角设置按钮 点击按钮后发生的事件:1. 控制器的跳转,进入新的控制器.view, 2. 跳转的时候对将要跳转的目标控制的子控件进行了布局.---通过手写plist文件的方式加载 为按钮注册单击 ...

  4. JavaScript手写new方法

    1.看一下正常使用的new方法 function father(name){ this.name=name; this.sayname=function(){ console.log(this.nam ...

  5. 推荐使用并手写实现redux-actions原理

    @ 目录 一.前言 二.介绍 2.1 创建action 2.2 reducer 2.3 触发action 三. 认识与手写createAction() 3.1 用法 3.2 原理实现 四.认识hand ...

  6. 手写系列-实现一个铂金段位的 React

    一.前言 本文基于 https://pomb.us/build-your-own-react/ 实现简单版 React. 本文学习思路来自 卡颂-b站-React源码,你在第几层. 模拟的版本为 Re ...

  7. 如何手写一个js工具库?同时发布到npm上

    自从工作以来,写项目的时候经常需要手写一些方法和引入一些js库 JS基础又十分重要,于是就萌生出自己创建一个JS工具库并发布到npm上的想法 于是就创建了一个名为learnjts的项目,在空余时间也写 ...

  8. 前端面试手写代码——call、apply、bind

    1 call.apply.bind 用法及对比 1.1 Function.prototype 三者都是Function原型上的方法,所有函数都能调用它们 Function.prototype.call ...

  9. 前端面试手写代码——模拟实现new运算符

    目录 1 new 运算符简介 2 new 究竟干了什么事 3 模拟实现 new 运算符 4 补充 预备知识: 了解原型和原型链 了解this绑定 1 new 运算符简介 MDN文档:new 运算符创建 ...

随机推荐

  1. 这些表情包你有吗?来 Battle 啊

    "能用表情包解决的问题,绝不多说一个字." "当不知道回复什么的时候,甩过去一个表情包就好了." 放眼望去,谁的 QQ/微信 收藏中没有几页代表性的表情包,那真 ...

  2. Debian 64位内核升级步骤

    安装相关依赖包 apt-get install bzip2 libncurses5-dev kernel-package zlib1g-dev gcc make kernel-package wget ...

  3. CSS属性(CSS盒子模型)

    1.CSS盒子模型 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset=&q ...

  4. Hibernate初识

    1. 持久化框架 狭义的概念:数据存储在物理存储介质不会丢失. 广义的概念:对数据的crud操作都叫持久化. 加载:hibernate的概念,数据从数据库中加载到session. 2. ORM(obj ...

  5. 都0202了,还在问Vegas和Pr哪个好?

    自媒体时代,蕴藏着很多机会.许多平凡的人,通过制作视频,收获了掌声.赢得了粉丝,甚至改变了自己的命运. 图1:B站百大UP主颁奖现场   但这条路真的一路畅通吗?其实不然,他们成功的背后,必定有多方面 ...

  6. FL Studio中的音频剪辑功能讲解

    音频剪辑,是FL Studio中的一个特色功能,音频剪辑的目的是保持在播放列表中显示和触发的音频,可以根据需要对它们进行切片和排列.但需要注意的是音频剪辑这个功能在FL Studio的基础版(果味版) ...

  7. 下载器Folx的创建种子功能怎么使用

    当我们想要分享一些自己制作的资源时,可以使用Folx的创建种子功能,在网络上分享种子,供他人下载,这个过程也被称为做种.作为种子创建者,需要在一定时间内保持做种进程,以便维持种子的生命期限,方便他人下 ...

  8. Java基础教程——线程池

    启动新线程,需要和操作系统进行交互,成本比较高. 使用线程池可以提高性能-- 线程池会提前创建大量的空闲线程,随时待命执行线程任务.在执行完了一个任务之后,线程会回到空闲状态,等待执行下一个任务.(这 ...

  9. 啊这......蚂蚁金服被暂缓上市,员工的大house没了?

      没有想到,网友们前两天才对蚂蚁员工人均一套大 House羡慕嫉妒恨,这两天又因为蚂蚁金服被叫停惋惜.小编看了一下上一篇的时间,正好是11月3日晚上被叫停.太难了!   这中间出现了什么变故呢?原本 ...

  10. javaAgent与pinpoint

    转载:https://www.cnblogs.com/kokov/p/12120033.html 项目中pinpoint应用: