高阶函数其实就是将函数作为参数传入,或者将函数作为输出的函数,在javascript中,函数也是对象,函数自然可以作为参数传递,并且在某些框架中,回调函数就会用到高阶函数的概念

简单的高阶函数

熟悉Array对象就会知道在其原型上存在很多方便的函数,map,filter等等,这些函数也可以通过自己来实现

  • filter
function filter(fn) {
const arr = []
for (let i = 0; i < this.length; i++) {
if (fn(this[i], i)) {
arr.push(this[i])
}
}
return arr
}
  • reduce
function reduce(fn, init) {
var a = init === undefined ? this[0] : init;
for (var i = init === undefined ? 1 : 0; i < this.length; i++) {
a = fn(a, this[i], i);
}
return a;
}
  • map
function map(fn) {
const arr = [];
for (let i = 0; i < this.length; i++) {
arr.push(fn(this[i], i));
}
return arr;
}
  • some
function some(fn) {
let res = false;
for (let i = 0; i < this.length; i++) {
res = res || fn(this[i], i);
}
return res;
}
  • every
function every(fn) {
let res = false;
for (let i = 0; i < this.length; i++) {
res = res && fn(this[i], i);
}
return res;
}

调用的时候只需要通过call将函数内部的this绑定为一个需要操作的数组就可以了

偏函数

偏函数就是当一个函数需要传入多个参数,但是其中的某些参数可以固定,实际只需要传入剩下参数的函数称为偏函数

偏函数通常可以使用bind来实现,通过bind,将原函数的某些参数固定,比如上面这个例子,通过bind使mult这个函数的第一个参数固定为2,bind的第一个参数的使用方式与call,apply的使用方式是一样的,用于指定函数内部的this,这里指定什么都可以,所以传入null

柯里化

柯里化的应用场景很广,可以将原本需要一次性传入多个参数的‘多元函数’转化为多个只需要传入一个参数的‘一元函数’ ,也叫分步式函数,形如 func(a,b,c,d,e) => func(a)(b)(c)(d)(e)

这样就实现了最简单的柯里化,通过不断返回函数的形式将参数传递形式暴露给外部,但是这样子虽然实现了分步式函数,但是代码耦合度太高,原函数有多少个参数就要写多少层return嵌套,可以利用arguments这个属性来递归实现任意参数函数的柯里化

无论是第一次初始化,还是在做递归调用时每一次传入的第一个参数一定要是原function
主要思路:首先把当前所有(除了原function)的参数暂存为args,然后返回一个函数,此时如果再外部调用返回的这个函数,那么将上一步所有参数与当前调用时传入的参数进行拼接,合成一个新newArgs,如果长度达到原function的length长度,则通过apply将newArgs传递进去

注意:此时apply的第一个参数为null,这样会强行将add函数的this指向改变为window

通过ES6写法将柯里化函数变得更简洁,完善

  • 反柯里化
Function.prototype.uncurrying = function() {
var that = this;
return function() {
return Function.prototype.call.apply(that, arguments);
}
}

反柯里化干的一件事就是将obj.func(a,b)转化成func(obj,a,b)

ES6简写版

反柯里化其实就是拓展了函数可调用的对象范围,比如map,之类的函数,只有在Array对象的prototype上存在,我一个string想调用怎么办,一般情况下是自己写一个就完事儿,函数反柯里化就是让你少写已经实现了的函数,举几个栗子吧

因为我们将uncurrying定义在Function的prototype上,所以所有函数都可以调用此方法

mapString('iAmString',(val)=>{
return {val}
})

等同于

'iAmString'.map((val)=>{
return {val}
})
//这样是错误

在函数柯里化和反柯里化的时候运用apply,call等手段改变this的指向

  • call/apply 实现
Function.prototype.call = function(context=window){
context[this.name]=this;
var arg = [...arguments].slice(1);
var res = context[this.name](...arg);
delete context[this.name];
return res;
}
Function.prototype.apply = function(context=window){
context[this.name]=this;
var res = null;
if(arguments[1]!==undefined){
res = context[this.name](...arguments[1])
}
else{
res = context[this.name]()
}
delete context[this.name];
return res;
}
  • bind

bind比较特殊,因为bind其实在使用时并不会立即执行前面的函数,bind会返回一个绑定好this的函数,且该函数可以作为普通函数和构造函数使用

Function.prototype.bind = function(context=window){
var fn = this;
var arg = [...arguments].slice(1);
function resFn (){
return fn.apply(this instanceof resFn ? this : context,arg.concat([...arguments]))
}
function tmp(){};
tmp.prototype = this.prototype;
resFn.prototype = new tmp();
}

当被用作构造函数时resFn的this是指向实例的,用作普通函数时,this是指向传进来的那个context的

第二个参数就是在做参数拼接,将bind时的传的参数和调用resFn时的参数进行拼接

当用作构造函数生成实例时,实例的__proto__指向resFn的prototype,所以将resFn的prototype设置为“原函数”(只有原函数prototype的中间函数)的一个实例即可,这样就算修改resFn的prototype也不会修改到原函数的prototype,又能很好的继承原函数的prototype上的属性

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注