注:文章最末尾有我的公众号二维码,会分享更多技术文章等,敬请关注javascript
本文讲解的高阶函数是以前讲解的闭包的续集,因此在学习高阶函数以前,必定要确保对闭包以及做用域的概念已经有了解:前端
闭包详解一git
有Java、C#等开发经验的同窗对代码抽象的思想必定不会陌生,抽象类、接口平时写的很是多,可是对于一直都从事前端开发的同窗来讲,“抽象”这个词就比较陌生了,毕竟JavaScript中没有abstract、interface。程序员
可是JS中确定是有代码抽象的思想的,只不过是形式上和Java等语言不一样罢了!github
先来看Java中的一个抽象类:ajax
public abstract class SuperClass {
public abstract void doSomething();
}
复制代码
这是Java中的一个类,类里面有一个抽象方法doSomething,如今不知道子类中要doSomething方法作什么,因此将该方法定义为抽象方法,具体的逻辑让子类本身去实现。编程
建立子类去实现SuperClass:设计模式
public class SubClass extends SuperClass{
public void doSomething() {
System.out.println("say hello");
}
}
复制代码
SubClass中的doSomething输出字符串“say hello”,其余的子类会有其余的实现,这就是Java中的抽象类与实现。数组
那么JS中的抽象是怎么样的,最为经典的就是回调函数了:
function createDiv(callback) {
let div = document.createElement('div');
document.body.appendChild(div);
if (typeof callback === 'function') {
callback(div);
}
}
createDiv(function (div) {
div.style.color = 'red';
})
复制代码
这个例子中,有一个createDiv这个函数,这个函数负责建立一个div并添加到页面中,可是以后要再怎么操做这个div,createDiv这个函数就不知道,因此把权限交给调用createDiv函数的人,让调用者决定接下来的操做,就经过回调的方式将div给调用者。
这也是体现出了抽象,既然不知道div接下来的操做,那么就直接给调用者,让调用者去实现。 和Java中抽象类中的抽象方法的思想是同样的。
总结一下抽象的概念:抽象就是隐藏更具体的实现细节,从更高的层次看待咱们要解决的问题。
在编程的时候,并非全部功能都是现成的,好比上面例子中,能够建立好几个div,对每一个div的处理均可能不同,须要对未知的操做作抽象,预留操做的入口,做为一名程序员,咱们须要具有这种在恰当的时候将代码抽象的思想。
接下来看一下ES5中提供的几个数组操做方法,能够更深刻的理解抽象的思想,ES5以前遍历数组的方式是:
var arr = [1, 2, 3, 4, 5];
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
console.log(item);
}
复制代码
仔细看一下,这段代码中用for,而后按顺序取值,有没有以为如此操做有些不够优雅,为出现错误留下了隐患,好比把length写错了,一不当心复用了i。既然这样,能不能抽取一个函数出来呢?最重要的一点,咱们要的只是数组中的每个值,而后操做这个值,那么就能够把遍历的过程隐藏起来:
function forEach(arr, callback) {
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
callback(item);
}
}
forEach(arr, function (item) {
console.log(item);
});
复制代码
以上的forEach方法就将遍历的细节隐藏起来的了,把用户想要操做的item返回出来,在callback还能够将i、arr自己返回:callback(item, i, arr)
。
JS原生提供的forEach方法就是这样的:
arr.forEach(function (item) {
console.log(item);
});
复制代码
跟forEach同族的方法还有map、some、every等。思想都是同样的,经过这种抽象的方式可让使用者更方便,同事又让代码变得更加清晰。
抽象是一种很重要的思想,让可让代码变得更加优雅,而且操做起来更方便。在高阶函数中也是使用了抽象的思想,因此学习高阶函数得先了解抽象的思想。
至少知足如下条件的中的一个,就是高阶函数:
将其余函数做为参数传递
将函数做为返回值
简单来讲,就是一个函数能够操做其余函数,将其余函数做为参数或将函数做为返回值。我相信,写过JS代码的同窗对这个概念都是很容易理解的,由于在JS中函数就是一个普通的值,能够被传递,能够被返回。
参数能够被传递,能够被返回,对Java等语言开发的同窗理解起来可能会稍微麻烦一些,由于Java语言没有那么的灵活,不过Java8的lambda大概就是这意思;
函数做为参数传递就是咱们上面提到的回调函数,回调函数在异步请求中用的很是多,使用者想要在请求成功后利用请求回来的数据作一些操做,可是又不知道请求何时结束。
用jQuery来发一个Ajax请求:
function getDetailData(id, callback) {
$.ajax('http://xxxxyyy.com/getDetailData?' + id, function (res) {
if (typeof callback === 'function') {
callback(res);
}
});
}
getDetailData('78667', function (res) {
// do some thing
});
复制代码
相似Ajax这种操做很是适合用回调去作,当一个函数里不适合执行一些具体的操做,或者说不知道要怎么操做时,能够将相应的数据传递给另外一个函数,让另外一个函数来执行,而这个函数就是传递进来的回调函数。
另外一个典型的例子就是数组排序。
在判断数据类型的时候最经常使用的是typeof,可是typeof有必定的局限性,好比:
console.log(typeof []); // 输出object
console.log(typeof {}); // 输出object
复制代码
判断数组和对象都是输出object,若是想要更细致的判断应该要使用Object.prototype.toString
console.log(Object.prototype.toString.call([])); // 输出[object Array]
console.log(Object.prototype.toString.call({})); // 输出[object Object]
复制代码
基于此,咱们能够写出判断对象、数组、数字的方法:
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
function isNumber(number) {
return Object.prototype.toString.call(number) === '[object Number]';
}
复制代码
咱们发现这三个方法太像了,能够作一些抽取:
function isType(type) {
return function (obj) {
return Object.prototype.toString.call(obj) === '[object ' + type + ']';
}
}
var isArray = isType('Array');
console.log(isArray([1,2]));
复制代码
这个isType方法就是高阶函数,该函数返回了一个函数,而且利用闭包,将代码变得优雅。
高阶函数在平时的开发中用的很是多,只是有时候你不知道你的这种用法就是高阶函数,在一些开源的类库中也用的不少,好比颇有名的 lodash,挑其中一个before函数:
function before(n, func) {
let result
if (typeof func != 'function') {
throw new TypeError('Expected a function')
}
return function(...args) {
if (--n > 0) {
result = func.apply(this, args)
}
if (n <= 1) {
func = undefined
}
return result
}
}
复制代码
在before函数中,同时有用到将函数当作传递进来,又返回了一个函数,这是一个很经典的高阶函数的例子。
看一下该代码能够怎么用吧:
jQuery(element).on('click', before(5, addContactToList))
复制代码
因此before函数就是让某个方法最多调用n次。
注:before函数代码不难,使用也不难,但就是这么一个简单的工具方法须要了解的知识点有:做用域、闭包、高阶函数,因此说知识点都是连贯的,接下来要写的JavaScript设计模式系列,一样也要用到这些知识。
在写代码的时候,大多数状况都是由咱们本身主动去调用函数。不过在有一些状况下,函数的调用不是由用户直接控制的,在这种状况下,函数有可能被废除频繁的调用,从而形成性能问题。
在 Element-UI 中,有一个 el-autocomplete 组件,该组件能够在用户输入的时候在输入框下方列出相关输入项:
其实就是能够在用户输入的时候,能够用已经输入的内容作搜索,饿了么在实现该组件的时候是利用input组件,而且监听用户的输入:
用input事件去监听用户输入的话,用户输入的每个字都会触发该方法,若是是要用输入的内容去作网络搜索,用户输入的每一字都搜索的话,触发的频率过高了,性能消耗就有点大了,并且在网络比较差的状况下用户体验也比较很差。
饿了么实现该组件的时候固然也考虑到了这些问题,用的是业界比较通用的作法→节流,就是当输入后,延迟一段时间再去执行搜索,若是该次延迟执行尚未完成的话,就忽略接下来搜索的请求。
看一下其实现:
autocomplete的节流思想就是刚才说的那种,而且用了 throttle-debounce 这个工具库,其实现就是利用高阶函数,有兴趣的同窗能够看它的源码:https://github.com/niksy/throttle-debounce,代码并不复杂。
高阶函数还有其余的用法,好比用在设计模式中等,这些内容将会在后面详细介绍。
能够关注个人公众号:icemanFE,接下来持续更新技术文章!