前端进阶之认识与手写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,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,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

最后

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


kindeditor4.x代码高亮功能默认使用的是prettify插件,prettify是Google提供的一款源代码语法高亮着色器,它提供一种简单的形式来着色HTML页面上的程序代码,实现方式如下: 首先在编辑器里面插入javascript代码: 确定后会在编辑器插入这样的代码: <pre
这一篇我将介绍如何让kindeditor4.x整合SyntaxHighlighter代码高亮,因为SyntaxHighlighter的应用非常广泛,所以将kindeditor默认的prettify替换为SyntaxHighlighter代码高亮插件 上一篇“让kindeditor显示高亮代码”中已经
js如何实现弹出form提交表单?(图文+视频)
js怎么获取复选框选中的值
js如何实现倒计时跳转页面
如何用js控制图片放大缩小
JS怎么获取当前时间戳
JS如何判断对象是否为数组
JS怎么获取图片当前宽高
JS对象如何转为json格式字符串
JS怎么获取图片原始宽高
怎么在click事件中调用多个js函数
js如何往数组中添加新元素
js如何拆分字符串
JS怎么对数组内元素进行求和
JS如何判断屏幕大小
js怎么解析json数据
js如何实时获取浏览器窗口大小
原生JS实现别踩白块小游戏(五)
原生JS实现别踩白块小游戏(一)