你也许不知道的javascript高级函数

更新日期: 2020-04-12阅读: 2.1k标签: 函数

前言

高阶函数是对其他函数进行操作的函数,可以将它们作为参数或通过返回它们。简单来说,高阶函数是一个函数,它接收函数作为参数或将函数作为输出返回。

例如Array.prototype.map,Array.prototype.filter,Array.prototype.reduce 都是一些高阶函数。

本文源自我的掘金 https://juejin.im/post/


尾调用和尾递归

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚。就是指某个函数的最后一步是调用另一个函数。

function g(x) {
  console.log(x)
}
function f(x) {
  return g(x)
}
console.log(f(1))
//上面代码中,函数f的最后一步是调用函数g,这就是尾调用。

上面代码中,函数 f 的最后一步是调用函数 g,这就是尾调用。尾调用不一定出现在函数尾部,只要是最后一步操作即可。

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生栈溢出错误。但是队伍尾递归来说,由于只存在一个调用帧,所以永远不会发生栈溢出错误。

function factorial(n) {
  if (n === 1) {
    return 1
  }
  return n * factorial(n - 1)
}

上面代码是一个阶乘函数,计算 n 的阶乘,最多需要保存 n 个调用数据,复杂度为 O(n),如果改写成尾调用,只保留一个调用记录,复杂度为 O(1)。

function factor(n, total) {
  if (n === 1) {
    return total
  }
  return factor(n - 1, n * total)
}

斐波拉切数列也是可以用于尾调用。

function Fibonacci(n) {
  if (n <= 1) {
    return 1
  }
  return Fibonacci(n - 1) + Fibonacci(n - 2)
}
//尾递归
function Fibona(n, ac1 = 1, ac2 = 1) {
  if (n <= 1) {
    return ac2
  }
  return Fibona(n - 1, ac2, ac1 + ac2)
}


柯理化函数

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术所谓柯里化就是把具有较多参数的函数转换成具有较少参数的函数的过程。
举个例子

//普通函数
function fn(a, b, c, d, e) {
  console.log(a, b, c, d, e)
}
//生成的柯里化函数
let _fn = curry(fn)

_fn(1, 2, 3, 4, 5) // print: 1,2,3,4,5
_fn(1)(2)(3, 4, 5) // print: 1,2,3,4,5
_fn(1, 2)(3, 4)(5) // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5) // print: 1,2,3,4,5

柯理化函数的实现

// 对求和函数做curry化
let f1 = curry(add, 1, 2, 3)
console.log('复杂版', f1()) // 6

// 对求和函数做curry化
let f2 = curry(add, 1, 2)
console.log('复杂版', f2(3)) // 6

// 对求和函数做curry化
let f3 = curry(add)
console.log('复杂版', f3(1, 2, 3)) // 6

// 复杂版curry函数可以多次调用,如下:
console.log('复杂版', f3(1)(2)(3)) // 6
console.log('复杂版', f3(1, 2)(3)) // 6
console.log('复杂版', f3(1)(2, 3)) // 6

// 复杂版(每次可传入不定数量的参数,当所传参数总数不少于函数的形参总数时,才会执行)
function curry(fn) {
  // 闭包
  // 缓存除函数fn之外的所有参数
  let args = Array.prototype.slice.call(arguments, 1)
  return function() {
    // 连接已缓存的老的参数和新传入的参数(即把每次传入的参数全部先保存下来,但是并不执行)
    let newArgs = args.concat(Array.from(arguments))
    if (newArgs.length < fn.length) {
      // 累积的参数总数少于fn形参总数
      // 递归传入fn和已累积的参数
      return curry.call(this, fn, ...newArgs)
    } else {
      // 调用
      return fn.apply(this, newArgs)
    }
  }
}

柯里化的用途

柯里化实际是把简答的问题复杂化了,但是复杂化的同时,我们在使用函数时拥有了更加多的自由度。 而这里对于函数参数的自由处理,正是柯里化的核心所在。 柯里化本质上是降低通用性,提高适用性。来看一个例子:

我们工作中会遇到各种需要通过正则检验的需求,比如校验电话号码、校验邮箱、校验身份证号、校验密码等, 这时我们会封装一个通用函数 checkByRegExp ,接收两个参数,校验的正则对象和待校验的字符串

function checkByRegExp(regExp, string) {
  return regExp.text(string)
}

checkByRegExp(/^1\d{10}$/, '18642838455') // 校验电话号码
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com') // 校验邮箱

我们每次进行校验的时候都需要输入一串正则,再校验同一类型的数据时,相同的正则我们需要写多次, 这就导致我们在使用的时候效率低下,并且由于 checkByRegExp 函数本身是一个工具函数并没有任何意义。此时,我们可以借助柯里化对 checkByRegExp 函数进行封装,以简化代码书写,提高代码可读性。

//进行柯里化
let _check = curry(checkByRegExp)
//生成工具函数,验证电话号码
let checkCellPhone = _check(/^1\d{10}$/)
//生成工具函数,验证邮箱
let checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/)

checkCellPhone('18642838455') // 校验电话号码
checkCellPhone('13109840560') // 校验电话号码
checkCellPhone('13204061212') // 校验电话号码

checkEmail('test@163.com') // 校验邮箱
checkEmail('test@qq.com') // 校验邮箱
checkEmail('test@gmail.com') // 校验邮箱

柯里化函数参数 length

函数 currying 的实现中,使用了 fn.length 来表示函数参数的个数,那 fn.length 表示函数的所有参数个数吗?并不是。

函数的 length 属性获取的是形参的个数,但是形参的数量不包括剩余参数个数,而且仅包括第一个具有默认值之前的参数个数,看下面的例子。

((a, b, c) => {}).length
// 3

((a, b, c = 3) => {}).length
// 2

((a, b = 2, c) => {}).length
// 1

((a = 1, b, c) => {}).length
// 0

((...args) => {}).length
// 0

const fn = (...args) => {
  console.log(args.length)
}
fn(1, 2, 3)
// 3


compose 函数

compose 就是组合函数,将子函数串联起来执行,一个函数的输出结果是另一个函数的输入参数,一旦第一个函数开始执行,会像多米诺骨牌一样推导执行后续函数。

const greeting = name => `Hello ${name}`
const toUpper = str => str.toUpperCase()

toUpper(greeting('Onion')) // HELLO ONION

compose 函数的特点

  • compose 接受函数作为参数,从右向左执行,返回类型函数
  • fn()全部参数传给最右边的函数,得到结果后传给倒数第二个,依次传递

compose 的实现

var compose = function(...args) {
  var len = args.length // args函数的个数
  var count = len - 1
  var result
  return function func(...args1) {
    // func函数的args1参数枚举
    result = args[count].call(this, args1)
    if (count > 0) {
      count--
      return func.call(null, result) // result 上一个函数的返回结果
    } else {
      //回复count初始状态
      count = len - 1
      return result
    }
  }
}

举个例子

var greeting = (name) =>  `Hello ${name}`
var toUpper = str => str.toUpperCase()
var fn = compose(toUpper, greeting)
console.log(fn('jack'))

大家熟悉的 webpack 里面的 loader 执行顺序是从右到左,是因为webpack 选择的是 compose 方式,从右到左依次执行 loader,每个 loader 是一个函数。

rules: [
  { test: /\.css$/, use: ['style-loader', 'css-loader'] }
]

如上,webpack 使用了 style-loader 和 css-loader,它是先用 css-loader 加载.css 文件,然后 style-loader 将内部样式注入到我们的 html 页面。

webpack 里面的 compose 代码如下:

const compose = (...fns) => {
  return fns.reduce(
    (prevFn, nextFn) => {
      return value =>prevFn(nextFn(value)) 
    },
    value => value
  )
}


链接: https://fly63.com/article/detial/8647

JavaScript 函数式编程

我理解的 JavaScript 函数式编程,都认为属于函数式编程的范畴,只要他们是以函数作为主要载体的。

Js函数式编程,给你的代码增加一点点函数式编程的特性

给你的代码增加一点点函数式编程的特性,最近我对函数式编程非常感兴趣。这个概念让我着迷:应用数学来增强抽象性和强制纯粹性,以避免副作用,并实现代码的良好可复用性。同时,函数式编程非常复杂。

让我们来创建一个JavaScript Wait函数

Async/await以及它底层promises的应用正在猛烈地冲击着JS的世界。在大多数客户端和JS服务端平台的支持下,回调编程已经成为过去的事情。当然,基于回调的编程很丑陋的。

JavaScript函数创建的细节

如果你曾经了解或编写过JavaScript,你可能已经注意到定义函数的方法有两种。即便是对编程语言有更多经验的人也很难理解这些差异。在这篇博客的第一部分,我们将深入探讨函数声明和函数表达式之间的差异。

编写小而美函数的艺术

随着软件应用的复杂度不断上升,为了确保应用稳定且易拓展,代码质量就变的越来越重要。不幸的是,包括我在内的几乎每个开发者在职业生涯中都会面对质量很差的代码。这些代码通常有以下特征:

javascript回调函数的理解和使用方法(callback)

在js开发中,程序代码是从上而下一条线执行的,但有时候我们需要等待一个操作结束后,再进行下一步操作,这个时候就需要用到回调函数。 在js中,函数也是对象,确切地说:函数是用Function()构造函数创建的Function对象。

js调用函数的几种方法_ES5/ES6的函数调用方式

这篇文章主要介绍ES5中函数的4种调用,在ES5中函数内容的this指向和调用方法有关。以及ES6中函数的调用,使用箭头函数,其中箭头函数的this是和定义时有关和调用无关。

JavaScript中函数的三种定义方法

函数的三种定义方法分别是:函数定义语句、函数直接量表达式和Function()构造函数的方法,下面依次介绍这几种方法具体怎么实现,在实际编程中,Function()构造函数很少用到,前两中定义方法使用比较普遍。

js在excel的编写_excel支持使用JavaScript自定义函数编写

微软 称excel就实现面向开发者的功能,也就是说我们不仅可以全新定义的公式,还可以重新定义excel的内置函数,现在Excel自定义函数增加了使用 JavaScript 编写的支持,下面就简单介绍下如何使用js来编写excel自定义函数。

js中的立即执行函数的写法,立即执行函数作用是什么?

这篇文章主要讲解:js立即执行函数是什么?js使用立即执行函数有什么作用呢?js立即执行函数的写法有哪些?

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!