资讯专栏INFORMATION COLUMN

JavaScript之柯里化

since1986 / 945人阅读

摘要:简介柯里化,又称部分求值,是把接收多个参数的函数变成接受一个单一参数最初函数的第一个参数的函数,并且返回接受剩余的参数而且返回结果的新函数的技术。按照作者的说法,所谓柯里化就是使函数理解并处理部分应用。的思想极大地助于提升函数的复用性。

简介
柯里化(Currying),又称部分求值(Partial Evaluation),是把接收多个参数的函数变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受剩余的参数而且返回结果的新函数的技术。

核心思想: 把多参数传入的函数拆成单参数(或部分参数)函数,内部再返回调用下一个单参数(或部分参数)函数,依次处理剩余的参数。

按照Stoyan Stefanov --《JavaScript Pattern》作者 的说法,所谓柯里化就是使函数理解并处理部分应用

在JavaScript中实现Currying

为了实现只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余参数的这句话所描述的特征。我们先实现一个加法函数add:

function add(x, y) {
  return x + y
}

我们现在实现一个被Curryingadd函数,命名该函数为curriedAdd,则根据上面的定义,curriedAdd需要满足以下条件:

curriedAdd(1)(3) === 4 // true

var increment = curriedAdd(1)
increment(2) === 3 // true

var addTen = curriedAdd(10)
addTen(2) === 12 // true

满足以上条件的curriedAdd函数可以用以下代码实现:

function curriedAdd(x) {
  return function(y) {
    return x + y
  }
}

当然以上实现有一些问题: 它不通用,并且我们并不想通过修改函数被人的方式来实现Currying化。

但是curriedAdd的实现表明了实现Currying的一个基础--Currying延迟求值的特性需要我们用到JavaScript中的作用域,说得更通俗一些,我们需要使用作用域(即闭包)来保存上一次传进来的参数。

curriedAdd进行抽象,可以得到如下函数currying:

function currying (fn, ...args1) {
  return function (...args2) {
    return fn(...arg1, ...arg2)
  }
}

var increment = currying(add, 1)
increment(2) === 3 // true

var addTen = currying(add, 10)
addTen(2) === 12 // true

在此实现中,currying函数的返回值其实是一个接受剩余参数并且立即返回计算值的函数。即它的返回值并没有自动被Currying。所以我们可以通过递归将currying返回的函数也自动Currying

function currying(fn, ...args) {
  if (args.length >= fn.length) {
    return fn(...args)
  }
  
  return function (...args2) {
    return currying(fn, ...args, ...args2)
  }
}

以上函数很简短,但是已经实现Currying的核心思想。JavaScript中常用库Lodash中的curry方法,其核心思想和以上并没有太大差异--比较多次接收的参数总数与函数定义时的形参数量,当接收的参数的数量大于或者等于被Currying函数的形参数量时,就返回运行结果,否则返回一个继续接受参数的函数

Currying应用场景 参数复用

固定不变的参数,实现参数复用是Currying的主要用途之一。

案例一

上文中的incrementaddTen的一个参数复用的实例。对add方法固定第一个参数为10后,该方法就变成了一个将接受累加10的方法。

案例二

判断对象的类型。例如下面这个例子:

function isArray (obj) {
  return Object.prototype.toString.call(obk) === "[object Array]"
}

function isNumber (obj) {
  return Object.prototype.toString.call(obj) === "[object Number]"
}

function isString (obj) {
  return Object.prototype.toString.call(obj) === "[object String]" 
}

// Test
isArray([1, 2, 3]) // true
isNumber(123) // true
isString("123") // true

但是上面方案有一个问题,那就是每种类型都需要定义一个方法,这里我们可以使用bind来扩展,优点是可以直接使用改造后的toStr:

const toStr = Function.prototype.call.bind(Object.prototype.toString)

// 改造前直接调用
[1, 2, 3].toString()    // "1,2,3"
"123".toString()    // "123"
123.toString()        // SyntaxError: Invalid or unexpected token
Object(123).toString()    // "123"

// 改造后调用 toStr
toStr([1, 2, 3])     // "[object Array]"
toStr("123")         // "[object String]"
toStr(123)         // "[object Number]"
toStr(Object(123))    // "[object Number]"

上面例子首先使用Function.prototype.call函数指定一个this值,然后.bind返回一个新的函数,始终将Object.prototype.toString设置为传入参数,其实等价于 Object.prototype.toString.call()

延迟执行

延迟执行也是Currying的一个重要使用场景,同样bind箭头函数也能实现同样的功能。
在前端开发中,一个常见的场景就是为标签绑定onClick,同时考虑为绑定的方法传递参数。
以下列出了几种常见的方法,来比较优劣:

通过 data 属性

通过data属性本质只能传递字符串的数据,如果需要传递复杂对象,只能通过 JSON.stringify(data)来传递满足JSON对象格式的数据,但对更加复杂的对象无法支持。(虽然大多数时候也无需传递复杂对象)

通过bind方法

bind方法和以上实现的currying 方法,在功能上有极大的相似,在实现上也几乎差不多。可能唯一的不同就是bind方法需要强制绑定context,也就是bind的第一个参数会作为原函数运行时的this指向。而currying不需要此参数。所以使用currying或者bind只是一个取舍问题。

箭头函数
handleOnClick(data))} />

箭头函数能够实现延迟执行,同时也不像bind方法必需指定context

通过currying
性能对比


通过jsPerf测试四种方式的性能,结果为:箭头函数 > bind > currying > trueCurrying
currying函数相比bind函数,其原理相似,但是性能相差巨大,其原因是bind由浏览器实现,运行效率有加成。

为什么不需要 Currying 1. Currying 的一些特性有其他解决方案

如果我们只是想提前绑定参数,那么我们有很多好几个现成的选择,bind箭头函数等,而且性能比Curring更好。

2. Currying 陷于函数式编程

Currying是函数式编程的产物,它生于函数式编程,也服务于函数式编程。

JavaScript并非真正的函数式编程语言,相比Haskell等函数式编程语言,JavaScript 使用Currying等函数式特性有额外的性能开销,也缺乏类型推导。

从而把JavaScript代码写得符合函数式编程思想和规范的项目都较少,从而也限制了 Currying等技术在JavaScript代码中的普遍使用。

结论

CurryingJavaScript中是低性能的,但是这些性能在绝大多数场景,是可以忽略的。

Currying的思想极大地助于提升函数的复用性。

Currying 生于函数式编程,也陷于函数式编程。假如没有准备好写纯正的函数式代码,那么Currying有更好的替代品。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106900.html

相关文章

  • JavaScript函数式编程,真香组合(一)

    摘要:组合的概念是非常直观的,并不是函数式编程独有的,在我们生活中或者前端开发中处处可见。其实我们函数式编程里面的组合也是类似,函数组合就是一种将已被分解的简单任务组织成复杂的整体过程。在函数式编程的世界中,有这样一种很流行的编程风格。 JavaScript函数式编程,真香之认识函数式编程(一) 该系列文章不是针对前端新手,需要有一定的编程经验,而且了解 JavaScript 里面作用域,闭...

    mengbo 评论0 收藏0
  • js 扩展 -- currying 柯里函数

    摘要:里也有柯里化的实现,只是平时没有在意。如果函数柯里化后虽然生搬硬套,不过现实业务也会有类似场景。 柯里化 先解释下什么是 柯里化 在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。 js 里也有柯里化的实现,只是平时没有在意。先把原文简介贴...

    Pocher 评论0 收藏0
  • 从一道面试题谈谈函数柯里(Currying)

    摘要:忍者秘籍一书中,对于柯里化的定义如下在一个函数中首先填充几个参数然后再返回一个新函数的技术称为柯里化。回到我们的题目本身,其实根据测试用例我们可以发现,函数的要求就是接受单一函数,例如但是与柯里化不同之处在于,柯里化返回的一个新函数。   欢迎大家再一次来到我的文章专栏:从面试题中我们能学到什么,各位同行小伙伴是否已经开始了悠闲的春节假期呢?在这里提前祝大家鸡年大吉吧~哈哈,之前有人说...

    cppprimer 评论0 收藏0
  • JavaScript专题函数柯里

    摘要:一个经常会看到的函数的实现为第一版我们可以这样使用或者或者已经有柯里化的感觉了,但是还没有达到要求,不过我们可以把这个函数用作辅助函数,帮助我们写真正的函数。 JavaScript 专题系列第十三篇,讲解函数柯里化以及如何实现一个 curry 函数 定义 维基百科中对柯里化 (Currying) 的定义为: In mathematics and computer science, cu...

    zhangfaliang 评论0 收藏0
  • 掌握JavaScript函数的柯里

    摘要:原文链接和都支持函数的柯里化函数的柯里化还与的函数编程有很大的联系如果你感兴趣的话可以在这些方面多下功夫了解相信收获一定很多看本篇文章需要知道的一些知识点函数部分的闭包高阶函数不完全函数文章后面有对这些知识的简单解释大家可以看看什么是柯里化 原文链接 Haskell和scala都支持函数的柯里化,JavaScript函数的柯里化还与JavaScript的函数编程有很大的联系,如果你感兴...

    DTeam 评论0 收藏0

发表评论

0条评论

since1986

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<