adodo0829/blog

函数类型(高阶函数)

Opened this issue · 0 comments

函数类型(高阶函数)

函数操作函数 => 就是高阶函数; 函数有时候我们理解为一系列规则的集合, 指令集.

# 通常为两种情况
1.函数作为参数
2.函数作为返回值

函数作为参数的场景

  • 回调函数callback
当把函数当作参数传递时, 我们可以抽离出一部分容易变化的业务逻辑(规则), 
把这部分业务逻辑放在函数参数中, 这样一来可以分离业务代码中变化与不变的部分,
做到规则的抽象与分离.

doSomething(cb) {
  // do something...
  // 不变的业务逻辑

  // then
  // 可变的业务逻辑, 可支持自定义
  cb()
}
# 典型的有 ajax 回调, 事件操作等

函数作为返回值的场景

  • 抽离重复功能
当把函数当返回值输出时, 意味着我们可以延续上一步的运算;
这样我们可以抽离出重复的的业务逻辑(规则), 做到简化代码的作用

let isString = function(obj) {
  return Object.prototype.toString.call(obj) === '[object String]';
};
let isArray = function( obj ){
  return Object.prototype.toString.call(obj) === '[object Array]';
};
let isNumber = function( obj ){
  return Object.prototype.toString.call(obj) === '[object Number]';
};
// 这些函数都有相同的部分, 那就是最后都调用Object.prototype.toString.call( obj )
// 我们抽离一下

let isType = function(type){ 
  return function(obj){
    return Object.prototype.toString.call(obj) === '[object '+ type +']';
  }
};
// 之后你可以isType('你想要的判断类型的规则')

高阶函数应用

对于 js 这类语言来说, 我其实比较喜欢且倾向函数式编程, 奈何功底有限,还是得慢慢修炼

AOP(面向切面编程)

把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块

在js中实现AOP,都是指把一个函数“动态织入”到另外一个函数之中

Function.prototype.before = function (beforefn) {
    var _this = this;    // 保存原函数的引用
    return function () {    // 返回包含了原函数和新函数的"代理"函数 
      beforefn.apply(this, arguments);    // 先执行新函数,修正this 
      return _this.apply(this, arguments);    // 再执行原函数
    }
  };
Function.prototype.after = function (afterfn) {
    var _this = this;
    return function () {
      var ret = _this.apply(this, arguments); //先执行原函数
      afterfn.apply(this, arguments); //再执行新函数
      return ret;
    }
  };
var func = function () {
    console.log(2);
  };
func = func.before(function () {
    console.log(1);
  }).after(function () {
    console.log(3);
  });

func(); // 1, 2, 3
# 把负责打印数字1和打印数字3的两个函数通过AOP的方式动态植入func函数

一些工具函数

  • 映射函数:返回的新函数将一个数组映射到另一个使用这个函数的数组上
function mapFunc(fn) {
  return function(arr) {
    return Array.prototype.map.call(arr, fn)
  }
}
let increase = item => item + 1
let increaseMap = mapFunc(increase)
let a = [1,2,3]
increaseMap(a) // [2,3,4]

函数柯里化(currying)

currying又称部分求值函数。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数(单个参数),刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值.

其主要用途在于参数复用。本质上是降低通用性,提高适用性。

  • 计算一周的消费,只需要在周末求和
# 理解: 每天调用该函数,保存累计值,最后一次调用时,求和输出; 我们可以判断参数
let everyWeekCost = (function() {
  // 闭包中用于保存数据的变量,本周每天的费用
  var feeArgs = []
  return function() {
    // 此函数才是最终执行的函数
    if (arguments.length === 0) {
      return feeArgs.reduce((prev, curr) => {
        return prev + curr
      }, 0)
    } else {
      Array.prototype.push.apply(feeArgs, arguments)
    }
  }
})()

everyWeekCost(1)
everyWeekCost(2)
everyWeekCost(3)
...
everyWeekCost(7)
everyWeekCost() // 1 + 2 + 3...+ 7的结果
  • 实现 add(1)(2)(3)() ==> 6
# 只要传了参数,就返回一个函数,并保存参数, 没有参数了就返回和值

function curryAdd() {
  let argsList = []
  // 返回一个闭包
  let closure = function () {
    // 本次调用传入的参数
    let args = [...arguments]
    if (args.length > 0) {
      // 保存参数
      argsList = argsList.concat(args)
      // 再次返回闭包,等待下次调用
      return closure
    }
    // 没有传递参数,执行累加
    return argsList.reduce((prev, cur) => prev + cur)
  }
  return closure
}

let add = curryAdd()
console.log(add(1)(2)(3)())
# 还有一种是利用函数对象的 toString 方法