高阶函数
1、什么样的函数是高阶函数?
1)一个函数的参数是另一个函数(回调)
2)一个函数返回另一个函数(拆分函数)
如 function a(){return function(){}}
2、常见的高阶函数:
1)before:我们经常会遇到这种需求,就是一个核心功能上面需要衍生出一些个性化功能,这时候,我们需要先将核心功能抽离出来,在外面再增加方法
解决方法:我们可以重写原型上的方法,加以扩展:
如我们现在有一个核心方法,toSay function toSay(...args){ console.log("say sth...",args) } 现在我们可以在toSay的原型上增加一个before方法,这样子所有的toSay方法也都可以调用 Function.prototype.before = function(beforeFn){ // 1、箭头函数中,没有this指向,所以会向上级作用于查找,这里的this就是调用before方法的对象。 // 2、箭头函数中,没有arguments参数,所以我们需要再return方法的时候,将参数获取,然后再箭头函数中传递使用。 return (...args)=>{ beforeFn(); this(...args); } } let newSay = toSay.before(()=>{ console.log("你好") }) newSay("111") // 你好 // 说话,[111]
上面这种将核心方法抽离出来,再在核心功能基础上封装新的方法的行为,可以理解为切片或者装饰。
2)react中的事物:即事物的改变,可以在前面和后面同时增加方法。
const perform=(anyFn,wrappers)=>{ wrappers.forEach(fn=>{ fn.init(); }); anyFn(); wrappers.forEach(fn=>{ fn.finish(); }); } perform(()=>{ console.log("说话") },[ { init:()=>{console.log("wrapper1...init")}, finish:()=>{console.log("wrapper1...finish")} }, { init:()=>{console.log("wrapper2...init")}, finish:()=>{console.log("wrapper2...finish")} } ] )
上面的例子中,perform函数接收两个参数,一个是需要执行的主体函数,一个是包裹的事物,在主体函数执行前,执行事物中的init方法,主体函数执行后,执行事物的finish方法。
3)柯里化:将一个函数拆成多个函数
比如说,我们现在需要判断一个对象的类型,最常用的方式是什么呢?
首先想到的就是函数的toString()方法
Object.prototype.toString().call(obj)
那么如何自定义实现判断某对象是否是某种类型的函数呢?
有了上面的toString方法,我们马上就能想到下面这种方法:
const checkType = (obj, type)=>{ return Object.prototype.toString().call(obj)==`[object ${type}]` // 注意这里的`` 必须是反单引号(英文状态下键盘上1旁边的符号) }
checkType('111','String') // true
上面这个函数实现了我们需要的功能,但是需要用户传入对象和对象类型,我们经常可能会输入错误的类型,如String类型,可能会被输入string,而我们还会觉得 是不是程序出问题了呢?
所以,我们能不能将上面的方法优化一下?不用用户输入对象类型,只用告诉用户调用对应函数名称,就可以知道知道对象是不是制定的类型呢?
我们将上面的函数修改一下,checkType1接收一个参数,type,然后返回一个函数,这个函数中,接收用户需要判断类型的对象。
然后用户只需要调用对应的isString或者是isNumber即可判断对应的数据类型。如下:
const checkType1 = (type)=>{
// 这里还涉及到了闭包的知识,即当前函数返回值,可以在任意作用域执行 return (obj)=>{ return Object.prototype.toString.call(obj)==`[object ${type}]` } } const isString = checkType1("String") const isNumber = checkType1("Number") isString("111") isNumber(111)
上面的方法看起来比前面的要好了一点,用户调用相对方便了一点点,但是我们的程序写的就相对啰嗦了,如果有很多种类型需要判断的话,意味着我们需要多次调用checkType1方法,传入不同的类型参数,并定义多个不同的变量去接收。
所以我们继续对上面的方法进一步优化:
const checkType1 = (type)=>{ return (obj)=>{ return Object.prototype.toString.call(obj)==`[object ${type}]` } } let types = ["isString","isNumber","isBoolean"] let utils = {}; types.forEach(t=>{ utils["is"+t] = checkType1(t); }); // 用户在调用时,使用utils.isString('111')这种方式即可
上面的方法种,我们在前面方法的基础上,做了一点改变,我们将所有类型通过一个数组储存起来,然后通过循环这个数组,分别给每个类型调用checkType1方法进行校验,并将校验的返回结果通过对象utils存储起来
后面用户想要判断某种对象的时候,只需要使用utils.isType即可。
这种做法的好处在于,不用再去分别手写对每种类型的判断,只需要将类型添加到数组中即可。
例如,现在有如下代码:
const add = (a,b,c,d)=>{ return a+b+c+d } add(1,2,3,4)
// 现在不想一起把参数传递完毕,希望分多次传递参数,比如,我们希望 add(1)(2)(3,4) 这样子传参执行,要如何实现?
// 这是一个典型的柯里化应用
const curring = (fn,arr=[])=>{
// fn 的参数个数,fn.length
const len = fn.length;
return (...args)=>{
// 这里的args就是每次传递的参数,用arr存储起来
arr = arr.concat(args);
// 如果传递的参数个数和计算函数的参数个数相等,则开始计算,否则继续调用curring函数,收集参数。
if(len==arr.length){
return fn(...arr);
}else{
return curring(fn, arr);
}
}
}
let newAdd = curring(add);
newAdd(1)(2)(3,4)
4)after函数:在做完一件事情后,执行某个函数。如调用一个函数3次后,通知另一个函数执行
1) 先定义一个希望三次后执行的函数 const fn= ()=>{ console.log("三次后执行我...") } 2) 定义一个函数,接受两个参数,一个times,一个需要执行的函数 const after= (times,fn)=>{ return ()=>{ if(--times==0){ fn() } } } let test = after(3, fn) test(); test(); test()'// 执行fn()
3、高阶函数的应用
前面列举了好几种高阶函数,具体有哪些情况下会使用高阶函数呢?
如我们常见的并发问题,发布订阅,还有常说的观察者模式,都是使用的高阶函数。
1)并发问题:如我们需要同时去读取两个文件内容,并在两个文件读取完毕后,将读取的内容打印出来。这里使用node的fs模块
最丑的代码是这样的 const fs = require("fs"); let info = {}; fs.readFile("name.txt","utf8",(err,data)=>{ if(err){ console.log(err); return; } info["name"] = data; fs.readFile("age.txt","utf8",(err,data)=>{ if(err){ console.log(err); return; } info["age"] = data; } console.log(info) }
上面代码,如果我们有多个文件需要读取的话,需要像金字塔一样,堆很远...这显然不是我们想要的结果,那么怎么样写的比较优雅一点呢?
上面我们讲了after函数,我们在读取完两个文件后,输出文件内容,和after函数的定义是不是很像?其实这种写法也很繁琐,但是不会像上面的方法一样一层层的堆叠了
const after = (times, fn)=>{ return ()=>{ if(--times==0){ fn() } } } const out=after(2,()=>{ console.log(info) }) fs.readFile("name.txt","utf8",(err, data)=>{ if(err){ console.log(err); return; } info["name"] = data; out(); }) fs.readFile("age.txt","utf8",(err, data)=>{ if(err){ console.log(err); return; } info["age"] = data; out() })