不高级的高阶函数

629 阅读5分钟

「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战

写在前面

高阶函数,听起来多么牛逼的名字啊,其实并没有听起来那么的触不可及。

本篇将带你逐步剖析高阶函数。

啥啥啥?

首先,我们先解释一下,这高阶函数是个啥:

  • 函数可以作为参数被传递

  • 函数可以作为返回值被输出

以上条件只需要满足一个,那就是高阶函数了(没想到吧~)

将函数作为参数传递

将函数作为参数来传递,这意味着我们可以将一些业务逻辑放在作为参数的函数中,从而分离业务代码中变化与不变的部分。

其中回调函数就是一个很常见的使用场景。

回调函数

在我们使用ajax的时候,回调函数的使用非常的常见。

当我们想要在获得ajax的返回后做一些事情的时候,因为不能确切的知道什么时候会拿到返回,因此常见的方式就是将要做的事情当成一个函数传入到ajax的请求中,待请求完成后执行callback方法。

看个🌰

var getUserInfo = function(id,cb){
    $.ajax('http://xxx.com/getUserInfo?' + userId,function(data){
        if(typeof cb === 'function'){
            cb(data)
        }
    })
}

getUserInfo(666,function(data){
    alert(data.userName)
})

在上面的例子中,我们可以看到,我们在获取到ajax请求后将获取到的用户名弹出。

其实,回调函数的应用不只有异步中使用。当某些操作不适合在某个函数中执行时,我们可以将这些操作抽成一个函数,并将这个函数作为参数传递个另一个函数,委托另一个函数来执行。

sort

Array.prototype.sort接受一个函数作为参数,该函数封装了对数组元素的排序规则。

sort方法的用途我们可以看出,我们要做的事情是对数组进行排序,这点是固定不变的。

而变化的是排序的规则,我们将这部分封装在函数参数里,动态传入sort方法,从而使Array.prototype.sort的排序变得非常的灵活。

[1,4,3,2].sort(function(a,b){
       return a-b
}) // [1, 2, 3, 4]

[1,4,3,2].sort(function(a,b){
       return b-a
}) // [4, 3, 2, 1]

函数作为返回值输出

相比把函数当作参数传递,函数当作返回值输出的应用场景更多,也更能体现函数式编程的巧妙。

判断数据的类型

当我们要判断一个数据类型时,将其抽成一个函数。

var isString = function( obj ){ 
    return Object.prototype.toString.call( obj ) === '[object String]';
}; 

var isArray = function( obj ){ 
    return Object.prototype.toString.call( obj ) === '[object Array]';
}; 

var isNumber = function( obj ){ 
    return Object.prototype.toString.call( obj ) === '[object Number]'; 
};

......

getSingle

我们用举一个单例模式的例子。

var getSingle = function(fn){
    var ret 
    return function() {
        return ret || (ret = fn.apply(this,arguments))
    }
}

在这个例子中,我们既把函数作为参数传递,又让函数执行后返回来另一个函数。

我们来看看效果

var getScript = getSingle(function(){
    return document.createElement('script')
})

var script1 = getScript()
var script2 = getScript()

console.log(script1 === script2) // true script1 和 script2 为创建的 script 标签

高阶函数实现AOP

我们需要先解释一下什么是AOP: 主要作用是将跟核心业务逻辑模块无关的功能抽离出来,也称作面向切面编程。

将这些功能抽离出来后,通过动态织入的方式掺入业务逻辑模块中,从而保持业务逻辑模块的纯净以及高内聚性。

通常我们在js中实现AOP,都是指把一个函数 动态织入 到另外一个函数之中,我们来举一个通过扩展Function.prototype来做到这一点的例子。

Function.prototype.before = function(beforefn){
    var that = this // 保存原函数的引用
    return function(){  // 返回包含了原函数和新函数的"代理"函数
        beforefn.apply(this,arguments) // 执行新函数,修正 this
        return that.apply(this,arguments) // 执行原函数
    }
}

Function.prototype.after = function(afterfn){
    var that = this
    return function(){
        var res = that.apply(this,arguments)
        afterfn.apply(this,arguments)
        return res
    }
}

var fun = function(){
    console.log(2)
}

fun = fun.before(function(){
    console.log(1)
}).after(function(){
    console.log(3)
})

fun()

// 1 2 3

这种使用AOP的方式来给函数添加职责其实也是js中一种特别巧妙的装饰者模式的实现。

后面有机会的话会给大家也说说装饰者模式。

其他应用

介绍一些常见的高阶函数的使用场景。

柯里化

先简单介绍一下柯里化,一个柯里化函数会接收一些参数,接收了这些参数后,函数不会立刻执行,而是会继续返回另一个函数,并将刚才传入的参数在函数形成的闭包中保存起来。等到函数被真正需要求值时,之前传入的所有参数会被一次性用于求值。

我们举个例子来方便理解

var address = (function(){
    let res = ''
    return function(){
        for(let val of arguments){
            res += val
        }
        return res
    }
})()

var curring = function(fn){
    let args = []
    return function(){
        if(!arguments.length){
            return fn.apply(this,args)
        }else{
            args = [...args,...arguments]
            return arguments.callee
        }
    }
}


address = curring(address)

address('中国')

address('江苏省')

address('无锡市')

address()

// '中国江苏省无锡市'

这样我们就完成了一个输入地址的柯里化函数,当调用address时,如果带上了参数则会将这些参数暂存起来,只有当不带参数时,才会进行真正的求值计算。

节流

当某些场景下,一些函数被频繁调用时,可能会造成很大的性能问题。

比如频繁点击按钮调用接口,window.onresizescroll等。

思路:我们频繁触发这些事件时,可能只需要在某段事件内只触发1次,这就需要我们按时间段来忽略一些事件。

代码实现:

function throttle(fun, wait) {
    let timeout = null;
    return function() {
      if (!timeout) { // 如果定时器不存在
        timeout = setTimeout(function(){ // 设置 wait时间后触发
          timeout = null; // 清除定时器
          fun() // 执行方法
        }, wait)
      }
    }
}

function test(){
  console.log('test')
}

// 我们以浏览器滚动为例 1s 触发一次
window.onscroll = throttle(test, 1000);

最后

以上,就是本人对高阶函数的一些浅显的理解,欢迎各路大佬拍砖。

同时,向大家推荐一个我参与的 vue3的移动端MD风格组件库,欢迎大家 star & pr