# 高阶函数

# 什么是高阶函数

  1. 一个函数的 参数 是一个函数 (回调)
  2. 一个函数 返回 一个函数 (拆分函数)

上面两个任意满足其一就是高阶函数

高阶函数示例:

// 一个函数的参数是一个函数 (回调)
function a() {}
a(() => {});
// 一个函数返回一个函数 (拆分)
function b() {
  return function() {};
}
// 例子 函数的before
// 希望将核心的逻辑提取出来,在外面再增加功能

// 重写原型上的方法(扩展)
Function.prototype.before = function(beforeFn) {
  return (...arg) => {
    // ...arg相当于[1,2,3,4] ...运算符 可以将参数收缩成数组也可以将数组展开为一个个参数
    // 箭头函数没有this指向,所以向外层寻找,也没有arguments所以上面用剩余运算符合并成一个数组作为一个参数传入
    beforeFn();
    this(...arg); // 这里的this就是当前调用的函数 也就是下面的say ...arg是展开运算符 相当于 say(1,2,3,4)
  };
};
// AOP 切片编程/装饰 把核心功能(say)抽离出来,在核心基础上增加功能(newSay)
const say = (...arg) => {
  // 抽离核心功能
  // ...arg是剩余运算符
  console.log("说话", arg);
};

const newSay = say.before(() => {
  console.log("您好");
});
const newSay1 = say.before(() => {
  console.log("hello");
});

newSay(1, 2, 3, 4); // 最后输出 您好 说话
newSay1();

# 函数柯里化

把一个大函数拆分多个函数(大概就是不停的返回函数)

函数柯里化实例:

// 柯里化: 把一个大函数拆分多个函数
// 高阶函数包含柯里化
// 类型判断 Object.prototype.toString.call() 一般实现
// console.log(Object.prototype.toString.call("123")); // [object String]
// console.log(Object.prototype.toString.call([123])); // [object Array]
// 一般封装实现
// const checkType = (content, type) => {
//   return Object.prototype.toString.call(content) === `[object ${type}]`;
// };
// const b = checkType(123, "Number");
// console.log(b);

// 柯里化实现 (基础版)
const checkType = type => {
  return content => {
    return Object.prototype.toString.call(content) === `[object ${type}]`;
  };
};

const isString = checkType("String"); // 返回的是内层函数
console.log(isString("123")); // 123是上面方法中的content参数

// 还可以进一步封装
const utils = {};
const types = ["Number", "String", "Boolean", "Array"];
// 下面直接用柯里化封装好的方法实现
// types.forEach(type => {
//   utils["is" + type] = checkType(type);
// });

// 函数柯里化怎么实现 通用的柯里化实现 (核心是把函数的参数保留起来,到该用的时候再用)
const add = (a, b, c, d, e) => {
  return a + b + c + d + e;
};
const curring = (fn, arr = []) => {
  // fn就是add
  return (...arg) => {
    let len = fn.length; // 函数的length就是参数的个数
    arr = arr.concat(arg); // [1,2] [1,2,3,4] [1,2,3,4,5]
    if (arr.length < len) {
      return curring(fn, arr);
    }
    return fn(...arr);
  };
};
let result = curring(add)(1, 2)(3, 4)(5);
console.log(result);
types.forEach(type => {
  utils["is" + type] = curring(checkType)(type);
});
console.log(utils.isArray(123));

# AOP (装饰模式) 将函数进行包装 (before,after)

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来。其实就是给原函数增加一层,不用管原函数的内部实现。

after实现:

// 在调用n次之后再执行
// after可以生成新的函数  等待函数执行次数达到我的预期时执行
const after = (times, fn) => () => --times === 0 && fn();

const newAfter = after(3, () => {
  console.log("调用三次后执行");
});

newAfter();
newAfter();
newAfter();

// 用上面这种方式可以优雅的处理node的并发问题 先看一下不优雅的方式
const fs = require("fs");
let info = {};
let index = 0;
function out() {
  if (index === 2) {
    console.log("很low的", info);
  }
}
fs.readFile("name.txt", "utf8", (err, data) => {
  info["name"] = data;
  index++;
  out();
});
fs.readFile("age.txt", "utf8", (err, data) => {
  info["age"] = data;
  index++;
  out();
});
// 优雅的实现方式
let afterInfo = {};
const outAfter = after(2, () => console.log("优雅的", afterInfo));
fs.readFile("name.txt", "utf8", (err, data) => {
  afterInfo["name"] = data;
  outAfter();
});
fs.readFile("age.txt", "utf8", (err, data) => {
  afterInfo["age"] = data;
  outAfter();
});

// 用发布订阅的方式实现
// 用on订阅,emit来发布实现
let e = {
  arr: [],
  on(fn) {
    this.arr.push(fn);
  },
  emit() {
    this.arr.forEach(fn => fn());
  }
};
let infoOnEmit = {};
e.on(() => {
  console.log("ok");
});
e.on(() => {
  if (Object.keys(infoOnEmit).length === 1) {
    console.log("这个订阅会在length为1的时候发布");
  }
});
e.on(() => {
  if (Object.keys(infoOnEmit).length === 2) {
    console.log("发布订阅实现", infoOnEmit);
  }
});
fs.readFile("name.txt", "utf8", (err, data) => {
  infoOnEmit["name"] = data;
  e.emit(); //  发布
});
fs.readFile("age.txt", "utf8", (err, data) => {
  infoOnEmit["age"] = data;
  e.emit();
});

# 发布订阅模式

(做解耦) 预先定义好一件事,等这件事发生的时候在执行 发布和订阅之间是没有关系的

发布订阅模式的应用 react 事务的封装

// react 事务的改变: 可以在前面和后面同时增加方法
// 开始的时候做某件事  结束的时候再做某件事
// 发布订阅的应用
const perform = (anymethod, wrappers) => {
  wrappers.forEach(wrap => {
    wrap.initilizae();
  });
  anymethod();
  wrappers.forEach(wrap => {
    wrap.close();
  });
};

perform(() => {
  console.log("核心功能");
}, [
  {
    initilizae() {
      console.log("开始时1");
    },
    close() {
      console.log("结束时1");
    }
  },
  {
    initilizae() {
      console.log("开始时2");
    },
    close() {
      console.log("结束时2");
    }
  }
]);

# 观察者模式

  • 观察者和被观察者是有联系的
  • 被观察者里面存了观察者
  • 观察者模式包含了发布订阅模式
// 被观察者
class Subject {
  constructor() {
    this.arr = [];
    this.state = "我不饿";
  }
  attach(o) {
    this.arr.push(o);
  }
  setState(newState) {
    this.state = newState;
    this.arr.forEach(o => o.update(newState));
  }
}

// 观察者
class Observer {
  constructor(name) {
    this.name = name;
  }
  update(newState) {
    console.log(`${this.name}  知道了 九儿  ${newState}`);
  }
}

let o1 = new Observer("Mopecat");
let o2 = new Observer("Sean");
let s = new Subject("九儿");
s.attach(o1);
s.attach(o2);
s.setState("又饿了");