当前位置: 首页 > 后端技术 > Node.js

【JS必知必知】高阶函数详解与实战

时间:2023-04-03 23:46:33 Node.js

前言一道经典面试题://JS实现一个无限累加的add函数add(1)//1add(1)(2)//3add(1)(2)(3)//6看到这道面试题,你是不是马上就想到用高阶函数来实现呢?想想在实际的项目开发过程中用到了哪些高级功能?你有没有想过自己创建一个高阶函数?本文开头的学习文章已经同步到github博客地址:程序员成长指南北区技术栈博客地址高阶函数定义高阶函数英文称为Higher-orderfunction。高阶函数是对其他函数进行操作的函数,可以将它们作为参数或返回它们。一个简单的总结就是,高阶函数就是接受一个函数作为参数或者返回一个函数作为输出的函数。作为参数的函数Array.prototype.map、Array.prototype.filter、Array.prototype.reduce和Array.prototype.sort是JavaScript中内置的高阶函数。他们将一个函数作为参数并将此函数应用于列表的每个元素。下面是对一些内置高阶函数的具体解释,以及与不使用高阶函数的情况的对比。Array.prototype.mapmap()(映射)方法最终生成了一个新的数组,并没有改变原数组的值。结果是对该数组中的每个元素调用提供的函数的结果。array.map(callback,[thisObject]);callback(回调函数)[].map(function(currentValue,index,array){//...});传递给map的回调函数(callback)接受三个参数分别是currentValue——被遍历的元素,index(可选)——元素索引,array(可选)——原始数组本身,另外也可以接受这个值(可选)tocallback,forexecution调用回调函数时使用的this值。我们举个简单的例子来理解,现在有一个数组[1,2,3,4],我们要生成一个新的数组,其中的每个元素都是之前数组大小的两倍,那么我们有下面两个使用高阶和不使用高阶函数的方法。不要使用高阶函数//koalaconstarr1=[1,2,3,4];常量arr2=[];for(leti=0;iitem*2);console.log(arr2);//[2,4,6,8]console.log(arr1);//[1,2,3,4]映射高阶函数注意回调需要有返回值,否则所有项都会映射到undefind;//kaolaconstarr1=[1,2,3,4];constarr2=arr1.map(item=>{});console.log(arr2);//[undefined,undefined,undefined,undefined]console.log(arr1);//[1,2,3,4]maphighorder对应函数的经典面试题//Outputresult["1","2","3"].map(parseInt);看完这道题,不知道大部分开发者是不是认为输出结果是[1,2,3],错误和正确的输出结果是:[1,NaN,NaN]分析解释因为map的回调函数有三个参数,被遍历的元素,元素索引(index),以及原始数组本身(array)。parseInt有两个参数,string和radix(基数)。注意当第二个参数为0或者没有参数时,parseInt()会根据字符串判断数字的基数。当省略参数radix时,JavaScript默认为数字的基数如下:如果字符串以“0x”开头,parseInt()会将字符串的其余部分解析为十六进制整数。如果字符串以0开头,ECMAScriptv3允许parseInt()的实现将以下字符解析为八进制或十六进制数字。如果字符串以1到9之间的数字开头,parseInt()会将其解析为十进制整数。如果只传入parseInt,map回调会自动忽略第三个参数数组。索引参数不会被忽略。未忽略的索引(0,1,2)将用作parseInt的第二个参数。拆开看看:parseInt("1",0);//上面说了第二个参数是base,所以"1",radix就是上面说的0,会被忽略,根据string1~9,parseInt()会将其解析为十进制整数1parseInt("2",1);//此时将2转为以1为底,因为超过1为底返回NaNparseInt("3",2);//此时将3转化为二进制数,因为这个数超过了1,所以返回NaN。所以最后的结果是[1,NaN,NaN]。那么想要得到[1,2,3]怎么写呢。["1","2","3"].map((x)=>{返回parseInt(x);});也可以简写为:["1","2","3"]。映射(x=>parseInt(x));为什么这样写就能返回想要的值呢?因为,传入一个完整的函数,带有形参和返回值。这样就不会因为参数输入错误导致结果错误,最终返回一个纯函数处理后的新数组。Array.prototype.reducereduce()方法为数组中的每个元素执行提供的reducer函数(按升序),将其结果聚合为一个返回值。传递给reduce的回调函数(callback)接受四个参数,分别是accumulator,currentValue——被操作的元素,currentIndex(可选)——元素索引,但是在开头会有特殊说明,array(可选)-原始数组本身,除了回调还可以接受初始值initialValue值(可选)。如果没有提供initialValue,那么第一次调用回调函数时,累加器使用原数组的第一个元素,currentValue为数组的第二个元素。在没有初始值的空数组上调用reduce会抛出错误。如果提供了initialValue,将作为回调函数第一次调用时第一个参数的值,即累加器,currentValue将使用原数组中的第一个元素。比如现在有一个数组[0,1,2,3,4],需要计算数组元素的和。要求比较简单。我们来看代码实现。不要使用高阶函数//koalaconstarr=[0,1,2,3,4];letsum=0;for(leti=0;i{returnaccumulator+currentValue;});console.log(sum);//10console.log(arr);//[0,1,2,3,4]以上是没有initialValue的情况,代码的执行过程如下,一共调用了四次回调。callbackaccumulatorcurrentValuecurrentIndexarray返回值firstcall011[0,1,2,3,4]1secondcall122[0,1,2,3,4]3thirdcall333[0,1,2,3,4]6fourthcall644[0,1,2,3,4]10有一个initialValue值。让我们看一下带有initialValue的情况。假设initialValue值为10,我们来看代码。//koalaconstarr=[0,1,2,3,4];letsum=arr.reduce((accumulator,currentValue,currentIndex,array)=>{returnaccumulator+currentValue;},10);console.log(sum);//20console.log(arr);//[0,1,2,3,4]代码的执行过程如下,一共调用了五次回调。回调累加器当前值当前索引数组返回值第一次调用1000[0,1,2,3,4]10第二次调用1011[0,1,2,3,4]11第三次调用1122[0,1,2,3,4]13第四次调用1333[0,1,2,3,4]16第5次调用1644[0,1,2,3,4]20Array.prototype.filterfilter(filter,filter)方法创建一个新数组,原数组不变。array.filter(callback,[thisObject]);其中包含由提供的函数实现的测试的所有元素。接收的参数和map一样,filter的回调函数需要返回一个布尔值true或者false。如果是真的,那就意味着通过了!如果为假,它的返回值是一个新数组,由所有通过测试的元素组成为真,如果没有数组元素通过测试,则返回一个空数组。举个例子,现在有一个数组[1,2,1,2,3,5,4,5,3,4,4,4,4],我们要生成一个新的数组,这个数组不需要重复的内容就是去重。不要使用高阶函数constarr1=[1,2,1,2,3,5,4,5,3,4,4,4,4];常量arr2=[];for(leti=0;i{returnself.indexOf(element)===index;});console.log(arr2);//[1,2,3,5,4]console.log(arr1);//[1,2,1,2,3,5,4,5,3,4,4,4,4]filter注意事项:过滤测试时回调是否必须是布尔值?例子:vararr=[0,1,2,3];vararrayFilter=arr.filter(function(item){returnitem;});控制台日志(arrayFilter);//[1,2,3]passed从例子中可以看出:只要filtertest的返回值弱等于==true/false就可以了,而不是返回===true/false.Array.prototype.sortsort()方法使用就地算法对数组的元素进行排序,并返回一个数组。这种排序方式会直接对原数组进行排序,不会生成新的排序后的数组。排序算法现在是稳定的。默认排序顺序是根据字符串Unicode代码点。//语法arr.sort([compareFunction])compareFunction参数是可选的,用于指定一个函数按一定顺序排列。注意这个函数有两个参数:参数一:firstEl用于比较的第一个元素。参数2:secondEl用于比较的第二个元素。请参见以下示例和说明://未指定的compareFunction函数['Google','Apple','Microsoft'].sort();//['Apple','Google','Microsoft'];//apple排名最后:['Google','apple','Microsoft'].sort();//['Google','Microsoft','apple']//无法理解的结果:[10,20,1,2].sort();//[1,10,2,20]//正确的结果[6,8,1,2].sort();//[1,2,6,8]//指定compareFunction函数'usestrict';vararr=[10,20,1,2];arr.sort(function(x,y){if(xy){return1;}return0;});console.log(arr);//[1,2,10,20]如果没有指定compareFunction,那么元素将被转换为字符串的字符按Unicode位置排序。例如,“Banana”将排在“cherry”之前。当数字从小到大排序时large,10出现在2之前,但是因为(没有指定compareFunction),比较的数会先转换为字符串,所以"10"在Unicode顺序上比"2"高。如果指定了compareFunction,数组将根据调用此函数的返回值进行排序。即a和b是两个要比较的元素:如果compareFunction(a,b)小于0,则a排在b之前;如果compareFunction(a,b)等于0,则a和b的相对位置保持不变。注意:此行为不受ECMAScript标准的保证,并非所有浏览器都会(例如Mozillapre-2003);如果compareFunction(a,b)大于0,b将排在a之前。compareFunction(a,b)必须始终对相同的输入返回相同的比较结果,否则排序的结果将是未定义的。sort排序算法的底层实现看完上面sort的排序介绍,想必小伙伴们会对sort排序算法的内部实现感兴趣。我在sf上搜索,发现了一些争议。于是查看了V8引擎的源码,在源码中找到了710行的源码地址:https://github.com/v8/v8/blob...//In-placeQuickSortalgorithm.//对于短(length<=22)数组,使用插入排序来提高效率。V8引擎的排序功能只提供两种排序方式:InsertionSort和QuickSort。小于或等于22的数组使用InsertionSort,大于22的数组使用QuickSort。有兴趣的可以看看具体的算法实现。注意:不同的浏览器引擎可能有不同的算法实现。我这里只是查看了V8引擎的算法实现。有兴趣的朋友可以查看其他开源浏览器的具体算法实现。如何改进排序算法,实现数字的正确排序?为了比较数字而不是字符串,比较函数可以简单地从a中减去b,下面的函数将数组按升序排序,并使用b-a降序排列。letcompareNumbers=function(a,b){returna-b;}letkoala=[10,20,1,2].sort(compareNumbers)console.log(koala);//[1,2,10,20]函数返回一个函数作为返回值输出,我们直接看两个例子加深理解。isType函数我们知道在判断类型的时候可以通过Object.prototype.toString.call获取对应对象返回的字符串,例如:letisString=obj=>Object.prototype.toString.call(obj)==='[objectString]';letisArray=obj=>Object.prototype.toString.call(obj)==='[objectArray]';letisNumber=obj=>Object.prototype.toString.call(obj)==='[对象编号]';可以发现上面三行代码中有很多重复的代码。只需要将具体的类型抽取出来,封装成判断类型的方法即可。代码如下。让isType=type=>obj=>{returnObject.prototype.toString.call(obj)==='[object'+type+']';}isType('String')('123');//trueisType('Array')([1,2,3]);//trueisType('Number')(123);//这里的true是一个高阶函数,因为isType函数会把obj=>{...}这个函数作为返回值输出。add求和函数前言中的面试题用JS实现了一个无限累加函数add。例子如下:add(1);//1添加(1)(2);//3add(1)(2)(3);//6分析面试题的结构,将函数作为返回值输出,然后接收新的参数并计算。我们知道打印函数的时候会自动调用toString()方法(不知道的可以看我的文章),函数add(a)返回一个sum(b)函数,累加计算a=a函数中的sum()+b,只需重写sum.toString()方法,返回变量a即可。functionadd(a){functionsum(b){//使用闭包a=a+b;//累加返回总和;}sum.toString=function(){//覆盖toString()方法returna;}返回总和;//返回一个函数}add(1);//1添加(1)(2);//3add(1)(2)(3);//6如何自己创建高阶函数前面我们讲了语言内置的各种高阶函数。我知道什么是高阶函数,以及那里有哪些类型的高阶函数。所以让我们自己创建一个高阶函数吧!假设JavaScript没有原生的map方法。我们构建自己的高阶函数,如map来创建我们自己的高阶函数。假设我们有一个字符串数组,我们想将它转换为一个整数数组,其中每个元素代表原始数组中字符串的长度。conststrArray=['JavaScript','PHP','JAVA','C','Python'];functionmapForEach(arr,fn){constnewArray=[];for(leti=0;i