JavaScript进阶之路系列(一): 高阶函数

看了这篇文章,你就会高阶函数了,是不是听起来很牛?高阶函数,听起来很高级,其实是很接地气,大家经常会用到的东西,比如filter,map,回调函数。

高阶函数是对其他函数进行操作的函数,可以将它们作为参数或通过返回它们。简单来说,高阶函数是一个函数,它接收函数作为参数或将函数作为输出返回。

在《javascript设计模式和开发实践》中是这样定义的: 1.函数可以作为参数被传递; 2.函数可以作为返回值输出。

举个例子:

function foo(f){          if(f instanceof Function){              f();          }      }      foo(function(){          alert("asdf");      })

定义了一个foo函数,foo函数传入了一个函数作为参数,foo函数里面添加判断,参数如果是函数就执行该参数。这就是一个简单的高阶函数。

回调函数(callback)

这个基本上是我们非常常用的高阶函数了,我们可以来看一下回调函数的代码:

function greeting(name) {    alert('Hello ' + name);  }    function processUserInput(callback) {    var name = prompt('请输入你的名字。');    callback(name);  }    processUserInput(greeting);

定义了一个greeting函数,又定义了一个processUserInput函数,之后我们又把greeting函数作为参数传到了processUserInput中,这个就符合了高阶函数中的第一个定义,作为参数被传递。

回调函数作为高阶函数中的一种,它是干什么的呢?

回调函数是指 使用者自己定义一个函数,实现这个函数的程序内容,然后把这个函数(入口地址)作为参数传入别人(或系统)的函数中,由别人(或系统)的函数在运行时来调用的函数。

这么说,可能听不明白。

callback 一词本来用于打电话。你可以打电话(call)给别人,也可以留下电话号码,让别人回电话(callback)。回调函数的意思就是,你写了一个函数,让别人来调用,就是回调函数。

来看一看异步的回调函数:

function ff(a, b, cbk) {      setTimeout(() => {          cbk(a + b);      }, 3000);  }  function f(callback) {      var x = 3, y = 4;      var z = 0;      callback(x, y, function (re) {          z = re;          console.log(z)      });      console.log("主函数")      return z;  }  f(ff);

对比来看,回调与同步、异步并没有直接的联系,回调只是一种实现方式,既可以有同步回调,也可以有异步回调,还可以有事件处理回调和延迟函数回调,这些在我们工作中有很多的使用场景。

我们可以像使用变量一样使用函数,作为另一个函数的参数,在另一个函数中作为返回结果,在另一个函数中调用它。当我们作为参数传递一个回调函数给另一个函数时,我们只传递了这个函数的定义,并没有在参数中执行它。

当包含(调用)函数拥有了在参数中定义的回调函数后,它可以在任何时候调用(也就是回调)它。

这说明回调函数并不是立即执行,而是在包含函数的函数体内指定的位置“回调”它(形如其名)。

回调函数是闭包的。

当作为参数传递一个回调函数给另一个函数时,回调函数将在包含函数函数体内的某个位置被执行,就像回调函数在包含函数的函数体内定义一样。闭包函数可以访问包含函数的作用域,所以,回调函数可以访问包含函数的变量,甚至是全局变量。

什么时候用回调函数?

我一般和别人合作项目的时候,想让人感觉我的代码写的很厉害,有时候会故意写两个回调,但是这种行为不可取的,减少代码冗余,无用的代码只会造成维护上的困难。

假如,你点击了一个按钮,你想让它执行连个函数,先执行函数A,再执行函数B,这时候就可以用回调函数了。

//定义主函数,回调函数作为参数  function A(callback) {    callback();    console.log('我是主函数');  }  //定义回调函数  function B(){    setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作  }  //调用主函数,将函数B传进去  A(B);

这里有一个问题,我们执行A函数后,执行B函数,为什么不直接在A函数里调用,要传参过去呢?

function A(callback) {   B();    console.log('我是主函数');  }    function B(){    setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作  }

这段代码的作用和回调函数好像是一样的。其实这两种方法在性能上是没有区别的,只是在灵活性上有很大的区别。 例如,我定义了一个C函数为回调函数。

//定义主函数,回调函数作为参数  function A(callback) {    callback();    console.log('我是主函数');  }  //定义回调函数  function B(){    setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作  }  //调用主函数,将函数B传进去  function C(){    setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作  }  //调用主函数,将函数C传进去  A(B);  A(C);

这时候,在试一下这段代码,就会出现很大的分歧。

function A(callback) {   B();   C();    console.log('我是主函数');  }    function B(){    setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作  }  function C(){    setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作  }

过滤器(filter)

这是另一种高阶函数,老样子,先看一下代码。

 array.filter(function(currentValue,index,arr), thisValue)

filter 接收一个函数作为参数,所以它也符合高阶函数中的第一个定义,作为参数被传递。

那什么是filter呢? filter()方法会创建一个新数组,原数组的每个元素传入回调函数中,回调函数中有return返回值,若返回值为true,这个元素保存到新数组中;若返回值为false,则该元素不保存到新数组中;原数组不发生改变。

简单来说,filter就是一个过滤数组的方法,符合条件的被传入新的数组,不符合条件的,就不管它了。

举个例子:

在一个Array中,删掉偶数,只保留奇数,可以这么写:

 var arr = [1, 2, 4, 5, 6, 9, 10, 15];   var r = arr.filter(function (x) {       return x % 2 !== 0;   });   r; // [1, 5, 9, 15]

定义了arr这个数组,arr数组使用的过滤器,过滤器中函数的作用就是把数组中的偶数过滤出来,放进r数组中。

使用filter,注意两点,1.filter() 不会对空数组进行检测;2. filter() 不会改变原始数组。

过滤器很强大,比如,我们面试经常会考的数组去重:

'use strict';     var r,arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];     r = arr.filter(function (element, index, self) {        return self.indexOf(element) === index;     });   console.log(r.toString()); //apple,strawberry,banana,pear,orange

map

map()方法定义在JavaScript的Array中,它返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。

注意: 1.map()不会对空数组进行检测;2.map()不会改变原始数组。

来看一看map的示例:

function pow(x){      return x*x;  }  var arr = [1,2,3,4,5,6];  var results = arr.map(pow);  console.log(results);      //[1,4,9,16,25,36]

定义了一个pow函数,函数的做用是让参数平方,map函数的参数是pow参数,map遍历了数组,把数组的每一项传进了pow函数里面,再return出来。

map也是一个典型的高阶函数。

总结:

高阶函数就只讲这两个方法了,我相信以各位的聪明才智已经理解了什么是高阶函数。

最后留一个小思考,闭包,是不是高阶函数呢?