高阶函数
简述
-
概念:
函数的参数是函数 || 函数返回了函数
-
典型应用场景:
为原有函数新增逻辑,而不破坏该函数的执行逻辑[AOP]
// 业务逻辑
function say(a,b){
console.log('say',a,b)
}
// 高阶函数,参数为函数,为函数添加方法并执行该方法。
Function.prototype.before = function(callback){
return (...args)=>{
// 1.切片函数,为函数提供前置逻辑;2....args传递原函数的参数
callback();
// 箭头函数保留原函数中的this => say
this(...args);
}
}
// 切片逻辑
let beforeSay = say.before(function(){
console.log('before say');
})
beforeSay('hello','world');
函数柯里化、反柯里化
核心思想
- 一种预先存储的思想,通过闭包实现。自定义条件,将逻辑细分,避免重复逻辑进行。
- 返回一个函数,函数保存了环境变量(前置逻辑),类似bind的逻辑!!!。
- 好处:变量复用,且不用重复执行重复逻辑
- 应用场景
- 根据浏览器类型返回不同的绑定事件的方法。
- 细分函数判断类型的方法。
场景实现:判断变量的类型。作用:细分方法,细化逻辑
- 相关知识点
- typeof -- 不能判断对象的类型
- constructor -- 通过该属性可以看到原变量是通过什么方式构造出来的,可以修改
- instanceof -- 判断该方法可以判断原变量是否为目标类型的实例
__proto__
- Object.prototype.toString.call()
例子:以参数数量为条件的柯里化
/*
通用型柯理化函数
当传入的参数满足形参数量时,函数执行。
否则返回媒介函数,继续接收参数[缓存参数信息]。
*/
const curring = (fn,arr= [])=>{
// ... 此处可以写若干逻辑判断
// 判断形参数量,只有当参数数量满足形参数量时,才执行函数
let len = fn.length;
return function(...args){
// arr为参数个数
let params = [...arr,...args];
// 自定义函数执行条件
if(params.length < len ){
// 持续返回一个函数,继续接收参数
return curring(fn,params)
}else{
// 接收到最后一个参数时,执行函数
return fn(...params)
}
}
}
// 例子:类型判断
const isTypeCurrying = (type,value) => {
console.log(value)
console.log(`type`,`[object ${type}]`)
return Object.prototype.toString.call(value) === `[object ${type}]`
}
const isArray = curring(isTypeCurrying)('Array');
const utils = {};
const typeArray = ['Number','String','Boolean','Null','Undefined'];
// 批量获取isType方法
typeArray.forEach(type=>{
utils['is' + type] = curring(isTypeCurrying)(type)
})
柯里化:事件绑定API兼容
// 例子:事件绑定方式判断
const whichEvents = (()=>{
if(window.addEventListen){
return function(ele,type,listener,useCapture){
ele.addEventListen(type,function(...args){
listener.call(ele,...args)
},useCapture)
}
}else{
return function(ele,type,handle){
ele.attachEvent('on'+type,function(...args){
handle.call(ele,...args)
})
}
}
})
同步函数迭代 -- compose函数
- 函数嵌套执行,并把上一个函数的执行返回结果传给下一个函数
const add = (x, y) => x + y;
const square = (x) => x * x;
// 简单包裹
const value = square(add(1, 2));
// 多函数复杂包裹 - 无异步
const compose = (...funcs) => {
let len = funcs.length;
if (len === 0) return (x) => x;
if (len === 1) return funcs[0](x);
// 普通写法
return (...args) => {
let last = funcs.pop();
let ret = last(...args);
funcs.forEach((fn) => {
ret = fn(ret);
});
return ret;
};
/*
redux源码
=> 逆推(cur放里面)
=> 第一轮 square(square(...args))
=> 第二轮 square(square(square(...args)))
=> 第三轮 square(square(square(add(...args))))
*/
// 大神写法
// return funcs.reduce((pre, cur) => {
// return (...args) => {
// return pre(cur(...args));
// };
// });
};
const fn = compose(square, square, square, add); // 先add再square
console.log(fn(1, 2));
同步函数迭代:经典数字累加
/*
例子:数字累加 -- add(1,2)(3)(4); add(1,2,3) add()
需求分析
函数能一直执行,所以返回的是函数,且逻辑相同,可能是函数本身。
后续返回的函数能保持之前的运算结果 -> 利用闭包锁定了作用域链,共用变量
重写函数转换数字的方法
将函数转为字符串经历三个步骤 Symbol.toPrimitive -> valueOf -> toString
在其中一个步骤重写方法即可
*/
const add = (...args)=>{
// 获取初始参数,拼接后续参数
let arr = [...args]
const fn = function(...args){
arr = [...arr,...args]; // arr为参数个数
return fn // 返回函数本身,保证递归
}
// 重写方法
fn[Symbol.toPrimitive] = ()=> arr.reduce((pre,cur)=>pre+cur);
return fn
}
console.log(+add(1,2,3,4));
console.log(+add(1,2)(3)(4));
console.log(+add(1)(2)(3,4,5));
异步函数迭代 -- 众多框架源码的核心思想之一[koa洋葱模型,async/await等]
// 多函数复杂包裹 - 含异步 co + generator
let finalFn = composeForNext(ctx, fns);
// console.log(finalFn)
// finalFn();
// fn1 fn2 fn1 - after-next fn3 fn2 -after-next
module.exports = composeForNext;
async function fn1(next) {
console.log("fn1");
await next();
console.log("fn1 - end");
}
async function fn2(next) {
console.log("fn2");
await delay();
await next();
console.log("fn2 - end");
}
async function fn3(next) {
console.log("fn3");
}
function delay() {
return new Promise((res) => {
setTimeout(() => {
res();
}, 2000);
});
}
const fns = [fn1, fn2, fn3];
const composeForNext = (middlewares) => {
// 返回一个将函数数组同步化的函数(回调地狱)
// 通过递归及promise,保证函数的执行顺序。
return function () {
// ctx上下文
// 执行递归函数
return dispatch(0);
function dispatch(i) {
// 根据索引获取函数体
let fn = middlewares[i];
// 如果无对应函数体,即最后一个next
if (!fn) {
// 返回空执行承诺,有可能上一个函数是异步函数,即await的,所以要保证返回的值是promise形式的。
return Promise.resolve();
}
// 如果有函数体
return Promise.resolve(
// 函数体执行,并且将下一个函数传入该函数体
fn(ctx, function next() {
// 传入ctx上下文到每个函数体
// 执行下一个函数体,并返回下一个函数的执行承诺,如果不返回则该函数体不会被放置在promise里,则失去同步性。
return dispatch(i + 1);
})
);
}
};
};