认识和手写compose方法(前端)

为什么要学习这个方法?

遇到这个方法主要是最近在阅读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 = funcscount
然后count--。再递归执行f1,则下面其实就是执行bb

result = funcscount
这样,就实现了 从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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352