高阶函数

一、什么样的函数是高阶函数?node

  1)一个函数的参数是另外一个函数(回调)react

  2)一个函数返回另外一个函数(拆分函数)数组

  如 function a(){return function(){}}闭包

二、常见的高阶函数:并发

  1)before:咱们常常会遇到这种需求,就是一个核心功能上面须要衍生出一些个性化功能,这时候,咱们须要先将核心功能抽离出来,在外面再增长方法app

    解决方法:咱们能够重写原型上的方法,加以扩展:函数

如咱们如今有一个核心方法,toSay
function toSay(...args){
    console.log("say sth...",args)
}
如今咱们能够在toSay的原型上增长一个before方法,这样子全部的toSay方法也均可以调用
Function.prototype.before = function(beforeFn){
    // 一、箭头函数中,没有this指向,因此会向上级做用于查找,这里的this就是调用before方法的对象。
    // 二、箭头函数中,没有arguments参数,因此咱们须要再return方法的时候,将参数获取,而后再箭头函数中传递使用。
    return (...args)=>{
        beforeFn();
        this(...args);
    }
}
let newSay = toSay.before(()=>{
    console.log("你好")
})
newSay("111") 
// 你好
// 说话,[111]

  上面这种将核心方法抽离出来,再在核心功能基础上封装新的方法的行为,能够理解为切片或者装饰。优化

  2)react中的事物:即事物的改变,能够在前面和后面同时增长方法。ui

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方法。this

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

三、高阶函数的应用

前面列举了好几种高阶函数,具体有哪些状况下会使用高阶函数呢?

如咱们常见的并发问题,发布订阅,还有常说的观察者模式,都是使用的高阶函数。

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() 
})
相关文章
相关标签/搜索