作者:chenhongdong

能来到这里就代表已经看过上一篇关于并不孤独的闭包文章了(没看不能来?当然……可以来),那么就不废话了,先来看下什么是高阶函数

高阶函数

  • 函数可以作为参数传递

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

函数作为参数传递

  • 回调函数

    • 在ajax异步请求的过程中,回调函数使用的非常频繁

    • 在不确定请求返回的时间时,将callback回调函数当成参数传入

    • 待请求完成后执行callback函数

下面看个简单的demo

说实在的本来只是个简单的????,不过越写越兴奋,就弄成了个小demo了,大家也可以copy下去自己添油加醋一下(写成各种版本),乐呵一下吧,PS:由于代码过多占用文章,将css样式去掉了,样式的实现大家随意发挥就好了

  • html结构

<body><div id="box" class="clearfix"></div><script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script><script src="./index.js"></script>
</body>
复制代码

js部分

// index.js
// 回调函数
// 异步请求
let getInfo = function (keywords, callback) {$.ajax({url: 'http://musicapi.leanapp.cn/search',  // 以网易云音乐为例data: {keywords},success: function (res) {callback && callback(res.result.songs);}})
};$('#btn').on('click', function() {let keywords = $(this).prev().val();$('#loading').show();getInfo(keywords, getData);
});
// 加入回车
$("#search_inp").on('keyup', function(e){if (e.keyCode === 13) {$('#loading').show();getInfo(this.value, getData);}
});function getData(data) {if (data && data.length) {let html = render(data);// 初始化Dom结构initDom(html, function(wrap) {play(wrap);});}
}
// 格式化时间戳
function formatDuration(duration) {duration = parseInt(duration / 1000);     // 转换成秒let hour = Math.floor(duration / 60 / 60),min = Math.floor((duration % 3600) / 60),sec = duration % 60,result = '';result += `${fillIn(min)}:${fillIn(sec)}`;return result;
}function fillIn(n) {return n < 10 ? '0' + n : '' + n;
}let initDom = function (tmp, callback) {$('.item').remove();$('#loading').hide();$('#box').append(tmp);// 这里因为不知道dom合适才会被完全插入到页面中// 所以用callback当参数,等dom插入后再执行callbackcallback && callback(box);
};let render = function (data) {let template = '';let set = new Set(data);data = [...set];    // 可以利用Set去做下简单的去重,可忽略这步for (let i = 0; i < 8; i++) {let item = data[i];let name = item.name;let singer = item.artists[0].name;let pic = item.album.picUrl;let time = formatDuration(item.duration);template += `<div class="item"><div class="pic" data-time="${time}"><span></span><img src="${pic}" /></div><h4>${name}</h4><p>${singer}</p><audio src="http://music.163.com/song/media/outer/url?id=${item.id}.mp3"></audio></div>`;}return template;
};let play = function(wrap) {wrap = $(wrap);wrap.on('click', '.item', function() {let self = $(this),$audio = self.find('audio'),$allAudio = wrap.find('audio');for (let i = 0; i < $allAudio.length; i++) {$allAudio[i].pause();}$audio[0].play();self.addClass('play').siblings('.item').removeClass('play');});
};
复制代码

按照上面的代码啪啪啪,就会得到下面这样的效果,一起来看下吧

image

友情提示:由于网易云音乐没有及时和周杰伦的娱乐公司关于版权使用问题续约,所以大家听不到周董的歌曲了,这个有点小遗憾啦!

不过依然感谢网易云音乐提供的API接口,让我们聆听美妙好音乐

  • 好了回归主旋律,前面的戏份有点过了,不知不觉居然写了个小demo,确实有点过分了

  • 本来是说一下函数作为参数传递的应用,写的太多了,赶紧调转船头继续讲吧

函数作为返回值输出

亲们,函数作为返回值输出的应用场景那就太多了,这也体现了函数式编程的思想。其实从闭包的例子中我们就已经看到了关于高阶函数的相关内容了,哈哈

还记得在我们去判断数据类型的时候,我们都是通过Object.prototype.toString来计算的。每个数据类型之间只是'[object XXX]'不一样罢了

所以在我们写类型判断的时候,一般都是将参数传入函数中,这里我简单写一下实现,咱们先来看看

function isType(type) {return function(obj) {return Object.prototype.toString.call(obj) === `[object ${type}]}
}const isArray = isType('Array');
const isString = isType('String');
console.log(isArray([1, 2, [3,4]]); // true
console.log(isString({});           // false
复制代码

其实上面实现的isType函数,也属于偏函数的范畴,偏函数实际上是返回了一个包含预处理参数的新函数,以便之后可以调用

另外还有一种叫做预置函数,它的实现原理也很简单,当达到条件时再执行回调函数

function after(time, cb) {return function() {if (--time === 0) {cb();}}
}
// 举个栗子吧,吃饭的时候,我很能吃,吃了三碗才能吃饱
let eat = after(3, function() {console.log('吃饱了');
});
eat();
eat();
eat();
复制代码

上面的eat函数只有执行3次的时候才会输出'吃饱了',还是比较形象的。

这种预置函数也是js中巧妙的装饰者模式的实现,装饰者模式在实际开发中也非常有用,再以后的岁月里我也会好好研究之后分享给大家的

好了,不要停,不要停,再来看一个栗子

// 这里我们创建了一个单例模式
let single = function (fn) {let ret;return function () {console.log(ret);   // render一次undefined,render二次true,render三次true// 所以之后每次都执行ret,就不会再次绑定了return ret || (ret = fn.apply(this, arguments));}};let bindEvent = single(function () {// 虽然下面的renders函数执行3次,bindEvent也执行了3次// 但是根据单例模式的特点,函数在被第一次调用后,之后就不再调用了document.getElementById('box').onclick = function () {alert('click');}return true;});let renders = function () {console.log('渲染');bindEvent();}renders();renders();renders();
复制代码

这个高阶函数的栗子,可以说一石二鸟啊,既把函数当做参数传递了,又把函数当返回值输出了。

单例模式也是一种非常实用的设计模式,在以后的文章中也会针对这些设计模式去分析的,敬请期待,哈哈,下面再看看高阶函数还有哪些用途

其他应用

函数柯里化

柯里化又称部分求值,柯里化函数会接收一些参数,然后不会立即求值,而是继续返回一个新函数,将传入的参数通过闭包的形式保存,等到被真正求值的时候,再一次性把所有传入的参数进行求值

还能阐述的更简单吗?在一个函数中填充几个参数,然后再返回一个新函数,最后进行求值,没了,是不是说的简单了

说的再简单都不如几行代码演示的清楚明白

// 普通函数
function add(x,y){return x + y;
}add(3,4);   // 7// 实现了柯里化的函数
// 接收参数,返回新函数,把参数传给新函数使用,最后求值
let add = function(x){return function(y){return x + y;}
};add(3)(4);  // 7
复制代码

以上代码非常简单,只是起个引导的作用。下面我们来写一个通用的柯里化函数

function curry(fn) {let slice = Array.prototype.slice,  // 将slice缓存起来args = slice.call(arguments, 1);   // 这里将arguments转成数组并保存return function() {// 将新旧的参数拼接起来let newArgs = args.concat(slice.call(arguments));    return fn.apply(null, newArgs); // 返回执行的fn并传递最新的参数}
}
复制代码

实现了通用的柯里化函数,了不起啊,各位很了不起啊。

不过这还不够,我们还可以利用ES6再来实现一下,请看如下代码:

// ES6版的柯里化函数
function curry(fn) {const g = (...allArgs) => allArgs.length >= fn.length ?fn(...allArgs) : (...args) => g(...allArgs, ...args)return g;
}// 测试用例
const foo = curry((a, b, c, d) => {console.log(a, b, c, d);
});
foo(1)(2)(3)(4);    // 1 2 3 4
const f = foo(1)(2)(3);
f(5);               // 1 2 3 5
复制代码

两种不同的实现思路相同,之后可以试着分析一下

不过大家有没有发现我们在ES5中使用的bind方法,其实也利用了柯里化的思想,那么再来看一下下

let obj = {songs: '以父之名'
};function fn() {console.log(this.songs);
}let songs = fn.bind(obj);
songs();   // '以父之名'
复制代码

为什么这么说?这也看不出什么头绪啊,别捉急,再来看一下bind的实现原理

Function.prototype.bind = function(context) {let self = this,slice = Array.prototype.slice,args = slice.call(arguments);return function() {return self.apply(context, args.slice(1));    }
};
复制代码

是不是似曾相识,是不是,是不是,有种师出同门的赶脚了啊

反柯里化

啥?反柯里化,刚刚被柯里化弄的手舞足蹈的,现在又出现了个反柯里化,有木有搞错啊!那么反柯里化是什么呢?简而言之就是函数的借用,天下函数(方法)大家用

比如,一个对象未必只能使用它自身的方法,也可以去借用原本不属于它的方法,要实现这点似乎就很简单了,因为call和apply就可以完成这个任务

(function() {// arguments就借用了数组的push方法let result = Array.prototype.slice.call(arguments);console.log(result);     // [1, 2, 3, 'hi']
})(1, 2, 3, 'hi');Math.max.apply(null, [1,5,10]);  // 数组借用了Math.max方法
复制代码

从以上代码中看出来了,大家都是相亲相爱的一家人。利用call和apply改变了this指向,方法中用到的this再也不局限在原来指定的对象上了,加以泛化后得到更广的适用性

反柯里化的话题是由我们亲爱的js之父发表的,我们来从实际例子中去看一下它的作用

let slice = Array.prototype.slice.uncurrying();(function() {let result = slice(arguments);  // 这里只需要调用slice函数即可console.log(result);    // [1, 2, 3]
})(1,2,3);
复制代码

以上代码通过反柯里化的方式,把Array.prototype.slice变成了一个通用的slice函数,这样就不会局限于仅对数组进行操作了,也从而将函数调用显得更为简洁清晰了

最后再来看一下它的实现方式吧,看代码,更逼真

Function.prototype.uncurrying = function() {let self = this;    // self 此时就是下面的Array.prototype.push方法return function() {let obj = Array.prototype.shift.call(arguments);/*obj其实是这种样子的obj = {'length': 1,'0': 1 }*/return self.apply(obj, arguments); // 相当于Array.prototype.push(obj, 110)}
};
let slice = Array.prototype.push.uncurrying();let obj = {'length': 1,'0': 1
};
push(obj, 110);
console.log(obj);   // { '0': 1, '1': 110, length: 2 }
复制代码

其实实现反柯里化的方式不只一种,下面再给大家分享一种,直接看代码

Function.prototype.uncurrying = function() {let self = this;return function() {return Function.prototype.call.apply(self, arguments);}
};
复制代码

实现方式大致相同,大家也可以写一下试试,动动手,活动一下筋骨

函数节流

下面再说一下函数节流,我们都知道在onresize、onscroll和mousemove,上传文件这样的场景下,函数会被频繁的触发,这样很消耗性能,浏览器也会吃不消的

于是大家开始研究一种高级的方法,那就是控制函数被触发的频率,也就是函数节流了。简单说一下原理,利用setTimeout在一定的时间内,函数只触发一次,这样大大降低了频率问题

函数节流的实现也多种多样,这里我们实现大家常用的吧

function throttle (fn, wait) {let _fn = fn,       // 保存需要被延迟的函数引用timer,          flags = true;   // 是否首次调用return function() {let args = arguments,self = this;if (flags) {    // 如果是第一次调用不用延迟,直接执行即可_fn.apply(self, args);flags = false;return flags;}// 如果定时器还在,说明上一次还没执行完,不往下执行if (timer) return false;timer = setTimeout(function() { // 延迟执行clearTimeout(timer);    // 清空上次的定时器timer = null;           // 销毁变量_fn.apply(self, args);}, wait);}
}window.onscroll = throttle(function() {console.log('滚动');
}, 500);
复制代码

给页面上body设置一个高度出现滚动条后试试看,比每滚动一下就触发来说,大大降低了性能的损耗,这就是函数节流的作用,起到了事半功倍的效果,开发中也比较常用的

分时函数

我们知道有一个典故叫做:罗马不是一天建成的;更为通俗的来说,胖纸也不是一天吃成的

体现在程序里也是一样,我们如果一次获得了很多数据(比如有10W数据),然后在前端渲染的时候会卡到爆,浏览器那么温柔的物种都会起来骂娘了

所以在处理这么多数据的时候,我们可以选择分批进行,不用一次塞辣么多,嘴就辣么大

下面来看一下简单的实现

function timeChunk(data, fn, count = 1, wait) {let obj, timer;function start() {let len = Math.min(count, data.length);for (let i = 0; i < len; i++) {val = data.shift();     // 每次取出一个数据,传给fn当做值来用fn(val);}}return function() {timer = setInterval(function() {if (data.length === 0) {    // 如果数据为空了,就清空定时器return clearInterval(timer);}start();    }, wait);   // 分批执行的时间间隔}
}// 测试用例
let arr = [];
for (let i = 0; i < 100000; i++) {  // 这里跑了10万数据arr.push(i);
}
let render = timeChunk(arr, function(n) {   // n为data.shift()取到的数据let div = document.createElement('div');div.innerHTML = n;document.body.appendChild(div);
}, 8, 20);render();
复制代码

惰性加载

兼容现代浏览器以及IE浏览器的事件添加方法就是一个很好的栗子

// 常规的是这样写的
let addEvent = function(ele, type, fn) {if (window.addEventListener) {return ele.addEventListener(type, fn, false);} else if (window.attachEvent) {return ele.attachEvent('on' + type, function() {fn.call(ele);});}
};
复制代码

这样实现有一个缺点,就是在调用addEvent的时候都会执行分支条件里,其实只需要判断一次就行了,非要每次执行都来一波

下面我们再来优化一下addEvent,以规避上面的缺点,就是我们要实现的惰性加载函数了

let addEvent = function(ele, type, fn) {if (window.addEventListener) {addEvent = function(ele, type, fn) {ele.addEventListener(type, fn, false);}} else  if (window.attachEvent) {addEvent = function(ele, type, fn) {ele.attachEvent('on' + type, function() {fn.call(ele)});}}addEvent(ele, type, fn);
};
复制代码

上面的addEvent函数还是个普通函数,还是有分支判断。不过当第一次进入分支条件后,在内部就会重写了addEvent函数

下次再进入addEvent函数的时候,函数里就不存在条件判断了

终点

节目不早,时间刚好,又到了该要说再见的时候了,来一个结束语吧

  • 高阶函数

    • 函数执行的分支仅会发生一次

    • 一次性加载太多太多数据,吃不消,可以像node中流一样,慢慢来,别急

    • 将频繁调用的函数设定在一个时间内执行,防止多次触发

    • 统一方法,让天下没有不能用的方法

    • 定义

    • 作用

    • 接收参数,返回新函数,把参数传给新函数,最后求值

    • 可以把函数当做参数传递和返回值输出

    • 函数柯里化

    • 反柯里化

    • 函数节流

    • 分时函数

    • 惰性加载

  1. 参数复用 (add函数栗子)

  2. 提前返回 (惰性加载)

  3. 延迟计算 (bind)

我勒个去,居然罗列了这么多东西,大家看的也很辛苦了,早睡早起,好好休息吧!

这两篇也是为了给观察者模式起个头,之后会继续写文章来和大家好好分享的,谢谢各位小主,阿哥的观看了!哈哈

- END-


文末彩蛋

扫一扫二维码回复,"2020",获得2020年最新前端、后端、大数据、人工智能、PHP等视频教程的百度云盘链接,获得之后,记得保存在自己的云盘里哦。


编程·前端·社区

高阶函数,太有用啦!相关推荐

  1. js最简单的几个特效_高阶函数不会用?教你JS中最实用的几个高阶函数用法

    不废话,先来看下什么是高阶函数 高阶函数 函数可以作为参数传递 函数可以作为返回值输出 函数作为参数传递 回调函数 在ajax异步请求的过程中,回调函数使用的非常频繁 在不确定请求返回的时间时,将ca ...

  2. Python进阶:函数式编程(高阶函数,map,reduce,filter,sorted,返回函数,匿名函数,偏函数)...啊啊啊...

    函数式编程 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计 ...

  3. python高阶函数看不懂_Python进阶:高阶函数的详细说明

    这篇文章讲述了Python进阶:高阶函数的详细说明有需要的朋友可以参考 函数式编程 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单 ...

  4. Kotlin学习笔记 第三章 函数 高阶函数 lambda表达式 内联函数

    参考链接 Kotlin官方文档 https://kotlinlang.org/docs/home.html 中文网站 https://www.kotlincn.net/docs/reference/p ...

  5. python 惰性序列_菜鸟学飞自学Python(五)高阶函数

    (仅个人学习摘抄) 函数式编程 函数式编程就是一种抽象程度很高的编程范式,特点是允许把函数本身作为参数传入到另一个函数,还允许返回一个函数. 高阶函数 高阶函数--Higher-order funct ...

  6. 廖雪峰python3高阶函数部分理解

    廖雪峰python3高阶函数部分理解 时间:2018年04月21日00:00:03 4.20,看了廖雪峰的python3教程,没有看太多,只看了高阶函数的map(前几天),reduce,filter, ...

  7. 如何在JavaScript中使用高阶函数

    将另一个函数作为参数的函数,或者定义一个函数作为返回值的函数,被称为高阶函数. JavaScript可以接受高阶函数.这种处理高阶函数的能力以及其他特点,使JavaScript成为非常适合函数式编程的 ...

  8. 高阶函数,你怎么那么漂亮呢!

    点击上方 "程序员小乐"关注, 星标或置顶一起成长 每天凌晨00点00分, 第一时间与你相约 每日英文 Sometimes, it is better to be alone. T ...

  9. 2021年大数据常用语言Scala(三十七):scala高级用法 高阶函数用法

    目录 高阶函数用法 作为值的函数 匿名函数 柯里化(多参数列表) 闭包 高阶函数用法 Scala 混合了面向对象和函数式的特性,在函数式编程语言中,函数是"头等公民",它和Int. ...

最新文章

  1. android底部滑出view,Android CoordinatorLayout与NestedScrollView基于Behavior几行代码实现底部View滑入滑出...
  2. 【Java 网络编程】NIO Buffer 简介 ( 概念 | 数据传输 | 标记 | 位置 | 限制 | 容量 | 标记 | 重置 | 清除 | 翻转 | 重绕 | 链式操作 )
  3. HDU 4121 Xiangqi (算是模拟吧)
  4. 构建微服务(Building Microservices)-PDF 文档
  5. 还在担心漏测吗?快来使用jacoco统计下代码覆盖率
  6. html中的rem做响应式,使用rem制作响应式网站
  7. ArcGIS学习总结(七)——河流制图综合
  8. 面向对象编程的四大特性
  9. Android学习视频集合
  10. javascript 实现简单计算器
  11. 肩部复杂的类人肌骨机器人手臂连杆机构
  12. c 与易语言程序间通信,易语言与三菱PLC通信-FX系列
  13. 10.1假期一半总结
  14. Android 屏幕适配扫盲、教程
  15. 穷苦人民如何用移动固态配置unbutu22.04
  16. C++——求平均数、求和函数
  17. 【gnuradio 仿真音频数据ASK调制和解调】
  18. python自动抓取网管软件的数据_python实现scrapy爬虫每天定时抓取数据的示例代码...
  19. k30最小宽度380不管用了_K30系列最强机,Redmi K30S至尊纪念版评测
  20. C语言 求众数 程序

热门文章

  1. 使用NAudio音频文件剪切指定片段
  2. 十进制转换成二进制java
  3. 大数据体系构建数据仓库
  4. 一文解决安装Anaconda后C盘不断增加的问题、修改默认配置
  5. 三里屯有优衣库也有程序员,猜猜他们在干嘛?
  6. Win7 突然没声音 无法播放测试音调
  7. ant design 动态给a-input设置默认值
  8. 指纹识别登录Windows Server服务器远程桌面
  9. 3451. 易位构词
  10. java 四边形_Java 实例 – 打印平行四边形