JavaScript进阶之高阶函数篇

欢迎大家来到woo爷说前端,今天给大家带来的是JavaScript进阶的知识,接下来的系列都是围绕着JavaScript进阶进行阐述;首先我们第一篇讲的是高阶函数。

高阶函数定义

高阶函数是指操作函数的函数;一般情况在项目开发过程中都会分两种情况

  1. 函数可以作为参数传递到另外一个函数执行
  2. 函数可以作为返回值输出被执行

让我们来用一张图描述一下高阶函数

以上是高阶函数的要求。我们在开发项目使用到的JavaScript的函数明显满足高阶函数的要求;因此我们在写代码过程中可以利用高阶函数对基本函数已有业务逻辑进行再次封装,或者作为回调函数。

接下来我们开始见识一下平时业务中怎么使用高阶函数;如何去封装高阶函数?

第一种模式:作为参数传递

在业务代码中经常遇到两个基本函数逻辑相同但业务逻辑不用的情况,我们可以把这两个相同的逻辑封装成一个高阶函数,从而把不同的逻辑写在函数里面作为参数传递到封装好的函数里面。这样可以实现业务逻辑一些变化一些不变的场景,这种是我们最常见的场景,简称为回调函数。

接下来让我们来举例子说明一下

例子1:

//两个不同的函数,但是其中一部分逻辑是相同的一部分是可变的     function a(){
    console.log("我是一个函数");
    console.log("我是a函数");        
  }
  
  
 function b(){
   console.log("我是一个函数");
   console.log("我是b函数")    
 }
 
 /*
 以上就是我们两个基本得函数,我们分别执行这两个函数
 */
 
 a();
 b()

这就是我们上面执行的结果,可以发现两个函数存在着相同点。那么我们接下来对两个函数进行下一步的处理:

function c(fn){
 console.log("我是一个函数")
  fn()  
}

//把相同的逻辑封装成一个c函数,不同逻辑的作为fn参数函数传递进去执行

c(function(){
   console.log("我是a函数") 
})

c(function(){
   console.log("我是b函数") 
})

//由此可见我们实现我们想要的呈现方式,接下来我们看一下执行的结果是怎么样的

这是我们最后执行的结果,跟上面的执行结果是一样的,可见高阶函数可以让代码更加多变性,更加简洁明了、易懂;这是我们平时常见的场景。

例子2:

其实我们还有一种场景更加经常在项目里面遇到。我们经常会在项目中使用ajax请求或者使用axios请求等等一些异步请求;一般往往我们不关心请求过程(请求过程是相同的)、只想要请求的结果处理不同业务逻辑。我们可以利用高阶函数对请求统一封装,也叫请求拦截。

var httpsAjax = function(obj,callback){
    var {url,data,type} = obj;
    $.ajax({
        url:url,
        type:type || 'POST' ,
        data:dara || {},
        success:function(res){
          //利用typeof 判断数据类型,如果是传进来的是函数,我们就执行回调函数
          if(typeof callback === 'function'){
             callback(res)
          }
        }   
   })
}

    httpsAjax({
        url:"xxx/get/user",
        type:"GET",
        data:{}
    },function(res){
      //操作一定的业务逻辑
      console.log(res)
   })

第一种模式总结:以上就是我们最常见的基本高阶函数的使用,一般我们会用函数作为参数传递到另外一个参数里面,然后另外一个参数执行传递进去的函数,从而形成了回调函数(高阶函数)。

第二种模式:作为返回值输出

相比把函数当作参数传递,函数当作返回值输出的应用场景也有很多。让函数继续返回一个可执行的函数,这样意味着运算过程是可延续的,就比如我们经常用到的数组排序Array.sort()方法。

下面是使用Object.prototype.toString方法判断数据类型一系列的isType函数例子:

var isString = function(obj){
  return object.prototype.toString.call(obj) === '[object String]'  
}


var isArray = function(obj){
  return object.prototype.toString.call(obj) === '[object Array]' 
}

var isNumber = function(obj){
  rturn object.prototype.toString.call(obj) === '[object Number]'  
}


isString("我是一个数组串");//true
isArray(["1","2"]);//true
isNumber(1)//true

注意:其实我们会发现上面的三个方法有大部分相同的逻辑object.prototype.toString.call(obj),不同的是处理逻辑返回的字符串结果,为了避免冗余的代码,我们将其封装成一个函数is_type()。

var is_type = function(obj,type){
  return object.prototype.toString.call(obj) == '[object ' + type + ']'  
}

console.log(is_type(11,'Number'));//true
console.log(is_type(['a','b'],"Array");//true

注意:上面就是我们进行封装的方法,可以发现我们提取出来之后,代码量少了很多,更加明确,但是我们会发现我们需要传递两个参数,而且两个参数的含义要一一对上,不然我们在业务代码上一旦写错没对应上,那我们写的逻辑就会出现bug。所以我们将这个方法再次封装一下,把type先区分类型作为参数传递进去,利用高阶函数拥有保存变量的作用,返回一个函数,分析我们传递的obj时到底是什么类型。

var isType = function(type){
    //先传递一个type参数作为数据类型,利用高阶函数拥有保存变量的特性,返回一个可持续执行的函数
    return function(obj){
          return object.prototype.toStirng.call(obj) === '[object '+ type +']'      
    }
}

//先细分到每种类型,这样我们就可以明确知道具体类型调用什么方法

var isString = isType("String");

var isArray = isType("Array");

var isNumber = isType("Number");

console.log(isArray([1,2,3]))//true

第二种模式总结:以上就是我们第二种模式的例子。显然而见我们可以利用这种模式让一个函数返回另外一个函数,让函数的运算继续延续下去,这就是第二种模式作为返回值输出。

以上就是高阶函数两种模式的基本理解与演示。相信对你在平时开发项目中有很多帮助;同时也要注意平时开发项目不是每个方法都要使用高阶函数,高阶函数使用场景就是以上两种,不然会导致大材小用。

接下来我们来讲一下JavaScript经常用到的高阶函数==map()/reduce()、filter()、sort()四个方法。

map()方法

  1. 定义:map()方法遍历原有数组返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值,按照原始数组元素顺序依次处理元素。
  2. 注意:不会对空数组进行检测、返回的是新数组不会改变原始的数组
  3. 语法格式 :
newArray.map(function(item){ 
    //遍历newArray item是newArray元素;类似于newArray[i]
  return item
  //必须有return,反正不会产生新的数组

})

map()方法定义在JavaScript的Array中,我们调用Array的map方法,传入我们自己想要的函数,就等到一个新的array作为结果,例子如下:

function pow(x){
  return x*x  
}

var arr = [1,2,3,4,5];
console.log(arr.map(pow));//[1,4,9,16,25]

这就是一个简单的map方式调用,传递一个pow自定义函数,对原数组每个元素进行乘幂,得到一个新的数组。

再比如我们平时经常会遇到这种,需要取数组对象中某个属性用来做一组数组:

var arr = [
    {
        name:"张三",
        type:2
    },
    {
         name:"李四",
         type:3
    }

]   

//要求拿到type属性组成新的数组


//以往的写法for循环
var new_arr = []
for(var i  = 0 ; i < arr.length ; i++){
    var obj = arr[i]
    for(var key in obj){
       if(key == 'type'){
           new_arr.push(obj[key])
        }
    }

}

//map的写法

var new_arr = arr.map(function(item){

    return item.type
    
}) 

从上面可以看到一个写法是for循环的写法,一个是map的写法;虽然两个得到的结果都是一样的[2,3];但是从代码量来说,for循环过于冗余,map简单方便。所以以后遇到这种场景可以使用map方法更加方便。

reduce()方法

  1. 定义:接收一个函数作为累加器,数组中的每一个值(从左到右)开始遍历,最终计算为一个值
  2. 注意:对空数组是不会执行回调函数的
  3. 语法格式 : new Array.reduce(callback,initialValue)

对语法格式的理解:

  • reduce(callback,initialValue)会传入两个变量,第一个参数是回调函数(callback)和第二个初始值(initialValue)。
  • 第一个参数回调函数(callback)有四个传入参数,prev和next,index和array。prev和next是必传的参数。
  • 第一个参数初始值(initialValue)决定回调函数的第一个参数prev的取值结果,当reduce传入initialValue时,prev的默认值就是initialValue,当reduce没有传入initialValue时,那么prev的默认值就是原数值的第一个元素值。

下面解释一下:

var arr = ["apple","orange"];

//第一种没有传递initialValue的情况
function getValue(){
  return arr.reduce(function(prev,next){
        console.log("prev",prev);
        console.log("next",next)
        return prev;
    })
}


console.log("getValue",getValue())

运行结果可以看出来我们没有传递initialValue的情况,prev取的是arr第一个元素值开始遍历。

接下来我们看一下传递initialValue的情况:

var arr = ["a","b"];


//传递initialValue情况时

function getValue(){
    return arr.reduce(function(prev,next){
       console.log("prev",prev);
       console.log("next",next);
       prev[next] =1;
       return prev;   
    },{})
    //initialValue传递一个空对象
}

console.log("getValue",getValue());

可以看到我们运行得结果,当传递initialValue的时候,prev默认值就是你传递的initialValue;而我们就可以利用传递的默认值进行一系列业务逻辑处理,达到我们想要的效果。

接下来我们来看一下经常业务中是怎么使用reduce()的。

案例1:计算数组总和

var numArray = [1,2,3,4,5];


//用for循环来计算
var num = 0;

for(var i = 0 ; i < numArray.length ; i++){
   num = num + numArray[i]  
}

console.log(num);//15

//利用reduce方法

var res = numArray.reduce(function(prev,next){
    return prev + next
},0)

console.log(res) ;//15

利用for循环我们要先声明一个全局变量作为默认值。我们知道开发项目过程中,尽量不要使用全局变量,而使用reduce我们可以完全避过,而且更加直接。这种是简单的使用reduce()。

案例2:合并二维数组

var arr = [[0,1],[2,3],[4,5]];

var res = arr.reduce(function(prev,next){
    return prev.concat(next)
},[])

console.log(res);//[0,1,2,3,4,5];

我们可以传递一个空数组作为默认值,对原始的数组元素进行合并成一个新的数组。

案例3:统计一个数组中的单词重复有几个

var arr = ["apple","orange","apple","orange","pear","orange"];


//不用reduce时

function getWorad(){
  var obj = {};
  for(var i = 0 ; i < arr.length ; i++){
    var item = arr[i];
    if(obj[item]){
      obj[item] = obj[item] + 1
    }else{
      obj[item] = 1
    }
  }
  return obj
}

console.log(getWorad());

function getWorad2(){
  return arr.reduce(function(prev,next){
    prev[next] = (prev[next] + 1 ) || 1;
    return prev;
  },{})
}
console.log(getWorad2())

最后两个的结果都是一样的,其实我们使用reduce()方法会让我们的逻辑变得简单易处理。从而抛弃我们冗余的代码,让代码看起来更加明了。相信你们再平时的业务中也会遇到这种业务逻辑的。

filter()方法

  1. 定义:filter()也是一个常用的操作,它用于把Array的某些元素过滤调,然后返回剩下的元素,和map方法类似,Array的filter也接收一个函数,和map()不同的是;filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢失该元素

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

var arr = [1,2,3,4,5];

var r = arr.filter(function(item){
   return item%2 !== 0 ;
})

console.log(r)

可以看到,我们利用filter对元素的过滤,只要元素取余不等于零,就返回来,等于零就抛弃,最后组成一个新的数组。

案例2:把一个arr中的空字符串去掉,可以这么写:

var arr = ['a','b','','c']
var r = arr.filter(function(item){
   return item && item.trim();
})

console.log(r) 

我们可以利用filter过滤数组中空的字符串,返回一个没有空字符串的数组。方便简单,接下来我们来解析一下filter方法回调函数所带的参数有哪些。

//先看一下filter的语法格式
var r = Array.filter(function(ele,index,_this){
    console.log(ele);
    console.log(index);
    console.log(_this);
    return ture;
})

/*

可见用filter()这个高阶函数,关键在于正确实现一个筛选函数
回调函数:filter()接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素,回调函数还可以接收另外两个参数,表示元素的位置,和数组本身。

*/

  业务中最经常用到的还是用filter来去除数组中重复的元素
var r,arr = [1,1,3,4,5,3,4];

r= arr.filter(function(ele,index,self){
   return self.indexOf(ele) === index;
})

console.log(r)

这样我们就很快速等到一个没有重复元素的数组。这也是我们经常遇到的去重,也是一种经常面试问到的。

sort()方法

  1. 定义:sort()方法用于对数组的元素进行排序,并返回数组。
  2. 语法格式:arrayObject.sort(sortby);
  3. 注意:参数sortby可选,用来规定排序的顺序,但必须是函数。
//接下来我们就来看一下到底如何排序

//从小到大的排序,输出:[1,2,3,9,56,87]
var arr = [9,87,56,1,3,2];
arr.sort(function(x,y){
   if(x < y){
      return -1
   }
   if(x > y){
      return 1
   }
      return 0;
})

//如果我们想要倒序排序;我们可以把大的放在前面[5,4,3,2,1]
var arr = [1,2,3,4,5];
arr.sort(function(x , y){
     if(x < y ){
        return 1
     }
     if(x > y){
        return -1
     }
      return 0
})

//在比如我们想要对字符串排序也可以实现,以字符串的首个字符,按照ASCII的大小    
//进行比较的,忽略大小写字母 ['good','apple','moide']

var arr = ['good','apple','moide'];

arr.sort(function(s1,s2){
    var x1 = s1.toUpperCase();//转成大写
    var x2 = s2.toUpperCase();
    if(x1 < x2){
       return -1
     }
     if(x1 > x2){
        return 1
     }
     return 0
})
//忽略大小写其实是把值转成大写或者全部小写,再去做比较

//注意:sort()方法会直接对原有的Array数组进行修改,他返回的结果仍是当前的Array    
//还有往往我们会在代码里见到很多数组对象,相对数组对象的某个属性做排序那要怎么实现呢
//下面就是对数组对象的排序例子

var arr = [
  {
     Name:'zopp',
     Age:10
   },
   {
      Name:'god',
      Age:1,
    },
    {
       Name:'ytt',
       Age:18
     }
];

//简单的写法;默认的升序

function compare(prototype){
 return function(a,b){
        var value1 = a[prototype];
        Var value2 = b[prototype];
        return value1 - value2
}
}
//一般我们往往会把参数函数单独写一个函数;达到清晰明确,多变性

console.log(arr.sort(compare(“Age”)));
/*最后我们写了这么多例子,也发现作为可变的部分参数函数其实无非就是两种;一种是升序一种是降序。其实我们还可以把这可变的参数函数封装成一个方法,利用传参的方式来说明我们是要升序还是降序
*/
/**数组根据数组对象中的某个属性值进行排序的方法 
     * 使用例子:newArray.sort(sortBy(type,rev,[,key])) 
     * @param type 代表newArray的类型,’number’表示数值数组[1,2],’string’表示字符串数组[‘abhs’,’as’],’obj’表示对象数组[{},{}],如果是obj的话第三个参数必填
     * @param rev true表示升序排列,false降序排序
     * @param key 非必填的第三个函数,是作为如果type是obj的话作为属性传递
     * */

var sortBy = function(type,rev,key){
    //第二个参数不传默认就是升序排列

   if(!rev){
     rev = 1
   }else{
     rev = rev ? 1 : -1;
    }
return function(a,b){
  //如果是string的话我们就要处理一下统一大写还是小写

  if(type == 'string'){
     a = a.toUpperCase();
     b = b.toUpperCase();
  }
  //如果是obj的话我们就是取对应的属性值
  if(type == 'obj'){
     a = a[key];
     b = b[key];
  }
   if(a < b){
     return rev * -1;
   }
   if(a > b){
     return rev * 1;
   }
    return 0;

   }
}

//这就是我们最后想要的统一封装,大家也可以拿着自己去修改一下,这种封装是最后返回一个处理好的匿名函数,也是高阶函数中的一种,后面我们也会提到。

总结: 如果要得到自己想要的结果,不管是升序还是降序,就需要提供比较函数了。该函数比较两个值的大小,然后返回一个用于说明这两个值的相对顺序的数字。

  • 比较函数应该具有两个参数 a 和 b,其返回值如下:
  • 若 a 小于 b,即 a - b 小于零,则返回一个小于零的值,数组将按照升序排列。
  • 若 a 等于 b,则返回 0。
  • 若 a 大于 b, 即 a - b 大于零,则返回一个大于零的值,数组将按照降序排列。
  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/2bHaGau1WHtbRFCtrj4p
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券