深入浅出 高阶函数 ☕️

深入浅出 高阶函数 ☕️

 看到本文的标题,应该有很多小伙伴看到 高阶函数 这个词语时, 心里可能会觉得很高大上, 其实很普通, 你可能接触过很多次了, 只不过可能你不知道这就是高阶函数.

那么何为 “高阶函数” 呢?

函数的形参如果可以为函数,又或者函数的返回值的一个函数式,这个函数就可以称之为 “高阶函数”.

也就是说, 高阶函数必须满足以下要求之一

  • 函数的形参可以为函数 ;
  • 函数的返回值可以为函数 ;

函数的形参可以为函数 ?

原生js中符合这个要求的高阶函数我们平常见得很多, 例如数组原型上的 forEach,map,sort等等方法又或者setInterval,setTimeout等API, 都是传入一个回调函数

函数的返回值可以为函数 ?

原生js中符合这个要求的高阶函数就有 Function.prototype.bind()

高阶函数的应用
1️⃣ 实现 AOP(面向切面编程)

什么是 AOP 呢?

? AOP 就是面向切面编程,是对 OOP 的补充。利用AOP可以对业务逻辑的各个部分进行隔离,也可以隔离业务无关的功能比如日志上报、异常处理等,从而使得业务逻辑各部分之间的耦合度降低,提高业务无关的功能的复用性,也就提高了开发的效率。 – 来自度娘?

通俗来讲,可以理解为在 JS中当你要执行某个函数时, 以这个函数为切点, 在该函数执行前还怎么做, 执行后又要怎么做的意思

结合日常生活来举栗子: 想必大家对"饭来张口 饭前洗手, 饭后漱口" 都不陌生. 这句话其实就是 AOP 在生活中的栗子: 吃饭这个动作相当于切点, 我们可以在这个切点前 , 后插入其它如洗手等动作.???

大概知道什么是 AOP 后,我们就用代码来封装下吧,
在 JavaScript中实现 AOP, 都是指把一个函数 “动态织入” 到另外一个函数之中, 具
体的实现技术有很多, 我们通过扩展 Function.prototype 来做到这一点. 代码如下:

Function.prototype.before = function (callback){
    var that = this; //先捕获调用该方法的函数
    //返回值为一个函数
    return function(){
        if(callback instanceof Function){
            callback.apply(this,arguments);
            that.apply(this,arguments);
        }
    }
}

Function.prototype.after = function (callback){
    var that = this;//先捕获调用该方法的函数
    //返回值为一个函数
    return function(){
        if(callback instanceof Function){
            that.apply(this,arguments);
            callback.apply(this,arguments);
        }
    }
}

function eat(){
    console.log('窝要恰饭~')
}

function washingHands(){
    console.log("洗手中...")
}

function gargle(){
    console.log('漱口中...')
}

var myBehavior = eat.before(washingHands).after(gargle)

myBehavior();

/*
    result: 
    
        洗手中...
        窝要恰饭~
        漱口中...

*/

以上是较为基础的装饰者模式,往往可以根据需求复杂化点.

? 装饰者模式: 在不改变原有对象的基础之上, 将功能附加到对象上.

2️⃣ 实现 currying (柯里化函数)

? currying 又称部分求值. 一个 currying 的函数首先会接受一些参数, 接受了这些参数之后,
该函数并不会立即求值, 而是继续返回另外一个函数, 刚才传入的参数在函数形成的闭包中被保
存起来. 待到函数被真正需要求值的时候, 之前传入的所有参数都会被一次性用于求值.

如果还有不了解什么是柯里化函数的小伙伴可跳转到《有兴趣了解柯里化吗,掉头发那种》文章了解详情,这里就不多讲了.

3️⃣ 实现防抖与节流函数

? 防抖与节流函数的作用就是来限制函数的触发频率的, 防止函数过频触发, 造成不必要的开销

由于以前写过相关的文章, 就不在重复BB了, 小伙伴可跳转到《闲谈函数之防抖与节流》文章了解详情, 这里就不多讲了.

4️⃣ 实现 uncurrying 反柯里化函数

? 反柯里化的作用是, 当我们调用某个方法, 不用考虑这个对象在被设计时, 是否拥有这个方法, 只要这个方法适用于它, 我们就可以对这个对象使用它. 其实和鸭子模型差不多.

  • 使用场景

上文说到 当我们调用某个方法, 不用考虑这个对象在被设计时, 是否拥有这个方法, 只要这个方法适用于它, 一般用于借用方法

举个?:

? 如果我要将函数内部的所有参数转化成一个数组, 这时就可以借用数组的slice(), [].slice.apply(arguments)

再来看下简单的代码思想,看完应该就会好懂点 emmm

Function.prototype.uncurrying = function(){
   var thatFn = this;//获取原函数
   
   return function(){
       var context = [].shift.call(arguments); //上下文环境
       return thatFn.apply(context,arguments)
   }
}

//利用反柯里化封装一个不定参加法函数吧
var transformArray = [].slice.uncurrying();

function addNums(){
   return transformArray(arguments).reduce(function(a,b){
       return a + b;
   })
}

//addNums(1,2,3) ===> 6

//addNums(666,555,233) ===> 1454

利用高阶函数实现的反柯里化函数是不是觉得挺有趣的,可能很多人比较喜欢直接借用方法,都是是否采用这些方法实现,主要取决于你的项目,有的时候他们确实可以让你事半功倍哈哈

5️⃣ 实现分时函数

说到分时这个名词,可能会有小伙伴会觉得他会不会和防抖/节流函数有一腿呢?

⚽️ 防抖函数一般用于搜索框,当用户每次输入不同的关键字进行不同的数据信息请求等场景;

⚾️ 节流函数一般用于拖拽移动等场景;

? 分时函数一般用户加载创建大量的DOM节点等场景;

举个?: 当你在浏览某个音乐网站时,点击进入某个含有成千上万首歌曲的歌单时,浏览器会一口气直接加载全部数据出来(没有分页的情况下),浏览器可能会吃不消而造成假死状态卡顿,会造成用户体验不足

那么假如我原本 1w+ 的数据, 一口子就加载实属不好, 我们可以按需分配或者分时加载, 也就是每隔一段时间你就给我加载一些数据, 做到弹性加载进而提升用户体验

不然就和国庆尾声的小学生一样,疯狂补作业(虽然我曾经也是???)

走流程,上代码

//假装这是歌单内部的歌曲条目信息
var musics = Array(10000).fill({
    title: '假装这是歌名',
    singer: '帅气的切图仔',
    duration: 250
})

//封装分时函数
/*
* @parame songList 歌单对象
* @parame createDomCallback 节点创建回调 第一个函数为加载歌曲对象
* @parame createNum 每次创建的数目
* @parame time 创建节点周期时间
*
*/

function loadSing(songList,createDomCallback,createNum,time){
    var timer = null;//定时器
    
    function create(){//遍历歌单
        for(var s = 0, sl = songList.length; sl <= Math.min(createNum || 1, sl); s++){
            createDomCallback(songList.shift())
        }
    }
    
    return function(){
        if(songList.length === 0){
            clearInterval(timer)//如果歌单数据为 0 则清除定时器
        }else{
            timer = setInterval(createDomCallback,time || 200)
        }
    }    
}

var showList = loadSing(musics,function createDom(){/* code */},100,500);

window.onload = showList; 

以上就是分时函数的简单封装和应用场景, 你 Get 到了吗 ???

6️⃣ 惰性加载函数

惰性加载函数: 惰性加载表示函数执行的分支仅会发生一次.

实现惰性加载的方式( 2种 ):

  • 在函数被调用时再处理函数
  • 在声明函数时就指定适当函数

使用场景: 惰性加载函数普遍用于API兼容管理, 随着浏览器以及设备的多样性和版本的高低, 有些 API 存在兼容问题, 由此就需要进行多分支判断, 以此达到一套代码,多端运行且功能一样的效果

最常见的兼容 API 就有 addEventListener,bind,forEach,requestAnimationFrame等等,就拿addEventListener来解释下

采用在函数被调用时再处理函数实现惰性加载函数

var addEvent = function(ele,type,callback,useCapture){
    if(addEventListener){
        return ele.addEventListener(type,callback,useCapture)
    }else{
        return ele.attachEvent('on' + type,callback.bind(ele))
    }
}

采用在声明函数时就指定适当函数实现惰性加载函数

var addEvent = (function(){
    if(addEventListener){
        addEvent = function(ele,type,callback,useCapture){
            return ele.addEventListener(type,callback,useCapture)
        }
    }else{
        addEvent = function(ele,type,callback,useCapture){
            return ele.attachEvent('on' + type,callback.bind(ele))
        }
})()

虽然有两种方式,但是我个人比较喜欢第二种,也就是在声明函数时就指定适当函数. 因为第一种封装方式有点多余,因为第一次调用时就知道采用哪个分支了,后续的使用完全不必要重复判断. 相对第二种就比较简单粗暴, 后续的兼容分支就按照第一次成功的分支进行即可,不会再次判断.

如果喜欢文章的话,不妨关注下公众号《进击的切图仔》,读取更多有趣的文章吧
进击的切图仔

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值