高阶函数及函数柯里化

1,345 阅读3分钟
1、什么时高阶函数(两个条件符合一个便是高阶函数)?

(1)一个函数返回一个函数(拆分函数)

(2)一个函数的参数是一个函数(回调)

例子:

函数的before(即在一个函数执行之前先执行其他逻辑代码)

Function.prototype.before = function (beforeFn) {
    return (...args) => { // 箭头函数中没有this指向 没有arguments 所以会像上级作用域查找
        beforeFn();
        this(...args); // 展开运算符 say(1,2,3)
    }
}
// AOP 切片 装饰 把核心抽离出来 在核心基础上增加功能
const say = (...args) => { // 剩余运算符把所有的参数组成一个数组
    console.log('说话', args);
}
const newSay = say.before(() => {
    console.log('您好')
})
const newSay1 = say.before(() => {
    console.log('天气很好')
})
newSay(1, 2, 3);
newSay1();

// 执行结果
// 您好
// 说话 [ 1, 2, 3 ]
// 天气很好
// 说话 []

代码解析:

(1)要实现执行一个函数之前先执行一段逻辑的话,即对该函数的功能进行扩展,所以一般会使用JavaScript的原型对函数进行扩展

(2)首先使用原型扩展出Function.prototype.before这个方法,接收一个函数作为参数并返回一个箭头函数

(3)在箭头函数的内部先执行我们想要执行的逻辑也就是say.before()传入的一个箭头函数

(4)箭头函数中没有this指向 没有arguments 所以会像上级作用域查找,即这里的this代表的就是say方法,并且newSay和newSay1接收的是Function.prototype.before返回的函数可以往里面传参数

(5)...args的两层含义:

​ a、剩余运算符把所有的参数组成一个数组

​ b、展开运算符 say(1,2,3)

2、React事务中用到的简单高阶函数

即对刚才的例子进行升级,函数执行之前执行一个初始化函数,函数执行之后执行关闭函数

例子:

// 事务 开始的时候 做某件事 结束的时候在做某件事
const perform = (anymethod, wrappers) => {
    wrappers.forEach(wrap => {
        wrap.initilizae();
    })
    anymethod();
    wrappers.forEach(wrap => {
        wrap.close();
    })
}
perform(() => {
    console.log('说话')
}, [{ // warpper
        initilizae() {
            console.log('您好')
        },
        close() {
            console.log('再见')
        }
    },
    { // warpper
        initilizae() {
            console.log('您好1')
        },
        close() {
            console.log('再见2')
        }
    }
])

// 执行结果
// 您好
// 您好1
// 说话
// 再见
// 再见2

代码解析:

(1)首先perform函数调用时传入一个函数和一个数组这两个参数,数组中包含多个对象,每个对象里包含初始化方法和关闭方法

(2)当执行perform函数内部逻辑时,先遍历传进来的数组,执行该数组中每个对象的初始化方法,再执行传进来的第一个函数的逻辑,最后再遍历数组,执行每个对象中的关闭方法

3、函数柯里化(简单版)

柯里化 我们可以把一个大函数拆分成很多的具体的功能

例子:

// 柯里化 : 就是将一个函数拆分成多个函数
// 判断类型 Object.prototype.toString.call

// 高阶函数中包含 柯里化  可以保留参数 bind
const checkType = type => {
  return content => {
    return Object.prototype.toString.call(content) === `[object ${type}]`;
  };
};

// 闭包
let types = ["Number", "String", "Boolean"];
let utils = {};
types.forEach(type => {
  utils["is" + type] = checkType(type);
});
console.log(utils.isString("123"));
console.log(utils.isNumber("456"));

// 输出结果
// true
// false

代码解析:

(1)在高阶函数中实现简单的函数柯里化,Object.prototype.toString.call方法可以用来判断一个内容的在js中的类型

(2)在checkType函数中使用了闭包来共享参数type,checkType也是一个高阶函数,该函数返回一个函数来执行判断类型的逻辑

(3)要实现多个类型的判断时,需要在调用checkType时,声明一个数组存储这些类型,并遍历出对应的类型来进行调用,生成一个新的函数如isString(),并添加进utils数组中

(4)这里重点解析一句代码:

 utils["is" + type] = checkType(type);

使用ES6对象数组语法,utils数组中添加types数组对应的类型属性作为键并将checkType函数作为值,形成键值对

输出utils对象数组的结果如下:

// { 
//  isNumber: [Function],
//  isString: [Function],
//  isBoolean: [Function] 
// }
4、函数柯里化(通用版)

例子:

// 通用的柯里化
const add = (a, b, c, d, e) => {
  return a + b + c + d + e;
};
const curring = (fn, arr = []) => {
  // 函数的length 为函数参数的个数
  let len = fn.length
  return (...args) => {
    arr = arr.concat(args); // [1]  [1,2,3] < 5
    if (arr.length < len) {
      return curring(fn, arr)  // fn  add()
     }
    return fn(...arr)
  }
}
let r = curring(add)(1)(2)(3)(4); // [1,2,3,4,5]
console.log(r);

// 输出结果
// [ 1, 2, 3, 4, 5 ]
// 15

代码解析:

​ 首先将add函数当作参数传进curring函数,curring函数返回一个可以接收可变参数的函数,然后将1,2,3,4,5每次传一个进去,数值参数进行拼接后,如果长度小于add函数的参数个数的话则继续递归调用curring函数,直到满足条件执行add函数将所有值相加。

5、函数柯里化(简单版的延伸)
const curring = (fn, arr = []) => {
  // 函数的length 为函数参数的个数
  let len = fn.length
  return (...args) => {
    arr = arr.concat(args); // [1]  [1,2,3] < 5
    if (arr.length < len) {
      return curring(fn, arr)  // fn  add()
     }
    return fn(...arr)
  }
}

const checkType = (type, content) => {
  return Object.prototype.toString.call(content) === `[object ${type}]`;
};
let types = ["Number", "String", "Boolean"];
let utils = {};
types.forEach(type => {
  utils["is" + type] = curring(checkType)(type); // 先传入一个参数
});
console.log(utils.isString('hello'));

// after 在...之后

// 输出结果
// [ 'String', 'hello' ]
// true

代码解析:

​ 类似上面的做法,只是多了一步将curring函数返回的函数当作键值对中的值传给utils对象数组中的对象

6、实现after...之后执行某函数

例子:

const after = (times, fn) => { // after可以生成新的函数 等待函数执行次数达到我的预期时执行
    return ()=>{
        if(--times === 0){
            fn();
        }
    }
};
let newAfter = after(3, () => {
  console.log("三次后调用");
});
newAfter();
newAfter();
newAfter();
// lodash after

// 输出结果
// 三次后调用

// 并发的问题  发布订阅 观察者模式

代码解析:

​ 因为after是一个高阶函数返回了一个新的函数,这个新的函数中只有当times降到0时才执行传进来的fn函数,fn函数即after调用传入的第二个参数(箭头函数),可用做功能计时器。