24

JavaScript 进阶之高阶函数篇

 4 years ago
source link: https://www.infoq.cn/article/2bHaGau1WHtbRFCtrj4p
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

高阶函数定义

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

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

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

nA3MRfF.png!web

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

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

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

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

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

例子 1:

复制代码

// 两个不同的函数,但是其中一部分逻辑是相同的一部分是可变的 function a(){
console.log(" 我是一个函数 ");
console.log(" 我是 a 函数 ");
}


functionb(){
console.log(" 我是一个函数 ");
console.log(" 我是 b 函数 ")
}

/*
以上就是我们两个基本得函数,我们分别执行这两个函数
*/

a();
b()

aaQBJfr.png!web

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

复制代码

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

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

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

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

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

3Aji63R.png!web

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

例子 2:

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

复制代码

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

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

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

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

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

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

复制代码

varisString =function(obj){
returnobject.prototype.toString.call(obj) ==='[object String]'
}


varisArray =function(obj){
returnobject.prototype.toString.call(obj) ==='[object Array]'
}

varisNumber =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){
returnobject.prototype.toString.call(obj) =='[object '+type+']'
}

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

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

复制代码

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

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

varisString = isType("String");

varisArray = isType("Array");

varisNumber = 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]
returnitem
// 必须有 return,反正不会产生新的数组

})

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

复制代码

functionpow(x){
returnx*x
}

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

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

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

复制代码

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

]

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


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

}

//map 的写法

varnew_arr = arr.map(function(item){

returnitem.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 的默认值就是原数值的第一个元素值。

下面解释一下:

复制代码

vararr = ["apple","orange"];

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


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

yiu6Jrv.png!web

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

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

复制代码

vararr = ["a","b"];


// 传递 initialValue 情况时

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

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

YnUrEf3.png!web

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

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

案例 1:计算数组总和

复制代码

varnumArray = [1,2,3,4,5];


// 用 for 循环来计算
varnum =0;

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

console.log(num);//15

// 利用 reduce 方法

varres = numArray.reduce(function(prev,next){
returnprev + next
},0)

console.log(res) ;//15

JniIFzm.png!web

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

案例 2:合并二维数组

复制代码

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

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

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

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

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

复制代码

vararr = ["apple","orange","apple","orange","pear","orange"];


// 不用 reduce 时

functiongetWorad(){
varobj = {};
for(vari =0; i < arr.length ; i++){
varitem = arr[i];
if(obj[item]){
obj[item] = obj[item] +1
}else{
obj[item] =1
}
}
returnobj
}

console.log(getWorad());

functiongetWorad2(){
returnarr.reduce(function(prev,next){
prev[next] = (prev[next] +1) ||1;
returnprev;
},{})
}
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){
returnitem%2!==0;
})

console.log(r)

Af6bM3N.png!web

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

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

复制代码

vararr = ['a','b','','c']
varr = arr.filter(function(item){
returnitem && item.trim();
})

console.log(r)

VFZzQzb.png!web

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

复制代码

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

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

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

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

console.log(r)

jqiaaiM.png!web

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

sort() 方法

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

复制代码

// 接下来我们就来看一下到底如何排序

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

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

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

vararr = ['good','apple','moide'];

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

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

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

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

functioncompare(prototype){
returnfunction(a,b){
varvalue1 = a[prototype];
Varvalue2 = b[prototype];
returnvalue1 - value2
}
}
// 一般我们往往会把参数函数单独写一个函数;达到清晰明确,多变性

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

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

if(!rev){
rev =1
}else{
rev = rev ?1:-1;
}
returnfunction(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){
returnrev *-1;
}
if(a > b){
returnrev *1;
}
return0;

}
}

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

总结:

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

  • 比较函数应该具有两个参数 a 和 b,其返回值如下:
  • 若 a 小于 b,即 a - b 小于零,则返回一个小于零的值,数组将按照升序排列。
  • 若 a 等于 b,则返回 0。
  • 若 a 大于 b, 即 a - b 大于零,则返回一个大于零的值,数组将按照降序排列。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK