Javascript高阶函数实践

205 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第30天,点击查看活动详情

  • 第一种情况:一个函数返回一个函数,那么这个函数就被称之为高阶函数。
function fn() {
  return function() {
    
  }
}
  • 第二种情况:一个函数的参数是函数,那么照顾函数就被称之为高阶函数。
function fn(cb) {
  cb()
}
fn(() => {})

我们利用高阶函数能解决什么问题?

1、进行函数的扩展

如果做一些扩展的时候,可能就会用到高阶函数

  • 底层的一个核心方法:在使用的时候,不希望改变原来的核心方法
function core(a, b, c) { // 底层方法,不希望被修改
  console.log("core", a, b, c)
}
  • 我们可以新增一层,对其进行扩展
// 使用
// newCore传入一个函数,希望在 core 之前执行
let newCore = core.before() => {
  console.log("before")
}
newCore(1, 2, 3)

实现 core.before

core.before = function(cb) { // cb 就是用户传入的函数(回调函数)
  return (...args) => {
    cb() // 先执行回调函数
    this(...args) // 再执行原始core方法:this会向上找到core(箭头函数没有this、arguments、prototype)
  }
}

此时运行的结果:

before
core

如果所有函数都希望有 before 方法的话

在函数原型 Function 上添加 before 方法

Function.prototype.before = function(cb) {
  return (...args) => {
    cb()
    this(...args)
  }
}

2、函数柯里化

例如:判断一个数据类型

  • typeof:只能判断基本的数据类型
    • 缺点:typeof null === 'object'
  • instanceof: xxx instanceof xxx
  • constructor:{}.constructor = Object[].constructor = Array
  • Object.prototype.toString.call
function isTyping(typing, value) {
  // [object Null]
  return Object.prototype.toString.call(value).slice(7) === typing
}
isTyping("String", "abc")

这种方式判断类型,每次都要传入类型字符串(例如 String、Number),很麻烦。我们可以利用闭包的机制来缓存变量。

闭包:定义函数的作用域和执行函数的作用域不一致,就会产生闭包

// 通过高阶函数暂存用户的参数
function isTyping(typing) {
  // typing = "String" 会一直保存在这里(闭包)
  return function(value) { // 调用之后,这里不会销毁,因为外界还保留着 isString 这个函数
    return Object.prototype.toString.call(value).slice(7) === typing
  }
}
let isString = isTyping("Stirng")
isString("abc")

封装到一个公用的对象中:

let util = {};
["String", "Number", "Boolean"].forEach(method => {
  util["is" + method] = isTyping(method);
})
util.isString("abc");
util.isNumber(123);

柯里化的概念

将函数的多个入参,将其变成了分批传入参数,而且参数每次只能传递一个(会让函数变得更具体一些)

例如:

sum(1, 2, 3, 4, 5, 6)
// 可以自己实现一个柯里化函数,变为
sum(1)(2)(3)(4)(5)(6)
// 或者另外一种情况:偏函数(分批传参),参数可能是多个(有人把偏函数也叫做柯里化)
sum(1, 2)(3, 4)(5)(6)

柯里化要求函数的参数个数是已知的。

实际应用

有多个接口,每次我们都需要记录接口的返回值:

  • 可以用数组,把每个接口的返回值存在数组中
  • 也可以用柯里化的思想,每次拿到返回值就传入柯里化函数,知道数据都传完了,再做某件事

题目

如何将 sum(1, 2, 3, 4, 5, 6) 转化为下面两种形式的调用

sum(1)(2)(3)(4)(5)(6)
sum(1, 2)(3, 4)(5)(6)

代码实现:

function add(...args) { // 求和函数
  return args.reduce((pre, cur) => pre + cur);
}
// 柯里化
function currying(fn) {
  let args = []
  return function temp(...newArgs) {
    if(newArgs.length) {
      args = [
        ...args,
        ...newArgs
      ]
      return temp
    } else {
      let value = fn.apply(this, args)
      args = [] // 保证下次调用时情况
      return value
    }
  }
}
// 调用方法
let sum = currying(add)
console.log(sum(1)(2)(3)(4)(5)(6)())
console.log(sum(1, 2)(3, 4)(5)(6)())

总结

高阶函数可以缓存变量、实现功能

  1. 高阶函数可以实现扩展功能
  2. 高阶函数可以实现参数的缓存功能