高阶函数

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() 
})

 

 
posted @ 2019-08-29 10:14  陌上花开缓缓归!  阅读(497)  评论(0编辑  收藏  举报
Top ↑