闭包详解二:JavaScript中的高阶函数

:文章最末尾有我的公众号二维码,会分享更多技术文章等,敬请关注javascript

本文讲解的高阶函数是以前讲解的闭包的续集,因此在学习高阶函数以前,必定要确保对闭包以及做用域的概念已经有了解:前端

理解抽象

引出抽象的概念

有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中的使用

高阶函数在平时的开发中用的很是多,只是有时候你不知道你的这种用法就是高阶函数,在一些开源的类库中也用的不少,好比颇有名的 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 组件,该组件能够在用户输入的时候在输入框下方列出相关输入项:

autocomplete.png

其实就是能够在用户输入的时候,能够用已经输入的内容作搜索,饿了么在实现该组件的时候是利用input组件,而且监听用户的输入:

实现autocomplete.png

用input事件去监听用户输入的话,用户输入的每个字都会触发该方法,若是是要用输入的内容去作网络搜索,用户输入的每一字都搜索的话,触发的频率过高了,性能消耗就有点大了,并且在网络比较差的状况下用户体验也比较很差。

饿了么实现该组件的时候固然也考虑到了这些问题,用的是业界比较通用的作法→节流,就是当输入后,延迟一段时间再去执行搜索,若是该次延迟执行尚未完成的话,就忽略接下来搜索的请求。

看一下其实现:

实现节流.png

autocomplete的节流思想就是刚才说的那种,而且用了 throttle-debounce 这个工具库,其实现就是利用高阶函数,有兴趣的同窗能够看它的源码:https://github.com/niksy/throttle-debounce,代码并不复杂。

高阶函数还有其余的用法,好比用在设计模式中等,这些内容将会在后面详细介绍。

特别注意

能够关注个人公众号:icemanFE,接下来持续更新技术文章!

公众号.png
相关文章
相关标签/搜索