Js柯里化菜鸟知识_柯里化小白帮助

简介柯里化(Currying),又称部分求值(Partial Evaluation),是把接收多个参数的函数变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受剩余的参数而且返回结果的新函数的技术。核心思想: 把多参数传入的函数拆成单参数(或部分参数)函数,内部再返回调用下一个单参数(或部分参数)函数,依次处理剩余的参数。按照Stoyan Ste

Js柯里化菜鸟知识

简介

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

Js柯里化菜鸟知识_柯里化小白帮助

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

在JavaScript中实现Currying

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

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

我们现在实现一个被Currying的add函数,命名该函数为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的主要用途之一。

案例一

上文中的increment、addTen的一个参数复用的实例。对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 属性

<div data-name="name" onClick={handleOnClick} />

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

通过bind方法

<div onClick={handleOnClick.bind(null, data)} />

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

箭头函数

<div onClick={() => handleOnClick(data))} />

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

通过currying

<div onClick={currying(handleOnClick, data)} />

性能对比

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

为什么不需要 Currying

1. Currying 的一些特性有其他解决方案

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

2. Currying 陷于函数式编程

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

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

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

结论

  1. Currying在JavaScript中是低性能的,但是这些性能在绝大多数场景,是可以忽略的。
  2. Currying的思想极大地助于提升函数的复用性。
  3. Currying 生于函数式编程,也陷于函数式编程。假如没有准备好写纯正的函数式代码,那么Currying有更好的替代品。
海计划公众号
(0)
上一篇 2020/03/24 05:44
下一篇 2020/03/24 05:44

您可能感兴趣的内容

  • TypeScript设计模式之享元模式小白基础_模式入门知识

    享元模式就是运行共享技术有效地支持大量细粒度的对象,避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类。在享元模式中有两个重要的概念,即内部状态和外部状态:内部状态:在享元对象内部不随外界环境改变而改变的共享部分。外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。由于享元模式区分了内部状态和外部状态,所以我们可以通过设置不同的外部状

    2020/03/20
  • jsoneditor菜鸟知识_JSON 在线编辑器插件

    jsoneditor菜鸟知识 官方网址:http://jsoneditoronline.org GitHub:https://github.com/josdejong/jsoned…

    2020/03/06
  • KaTeX入门知识_一个专门用于 web 的快速数学公式渲染工具库

    KaTeX入门知识 官方网址:https://katex.org GitHub:https://github.com/KaTeX/KaTeX 简介描述:一个专门用于 web 的快速…

    2020/03/06
  • 为什么要学习Typescript 语言呢?Typescript 开发环境安装新手入门_Typescript指南教程

    TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。为什么要学习Typescript 语言呢原因很简单,当然是为了工作呀,因为工作使我接触到了

    2020/04/03
  • 微信h5页面下拉露出网页来源的解决办法入门指南_微信入门教程

    微信h5页面下拉露出网页来源的解决办法入门指南 微信h5页面下拉露出网页来源的解决办法:将document的touchmove事件禁止掉 //禁止页面拖动 document.add…

    2020/03/19
  • jasmine基础入门教程_一款 JavaScript 测试框架,它不依赖于其他任何 JavaScript 组件

    jasmine基础入门 官方网址:http://jasmine.github.io/ GitHub:https://github.com/jasmine/jasmine 简介描述:…

    2020/03/05
  • CSS优先级的两种理解方式使用帮助_优先级基础入门

    方式一:值相加我们先去MDN看看官方的解释:优先级是如何计算的?优先级就是分配给指定的 CSS 声明的一个权重,它由 匹配的选择器中的 每一种选择器类型的 数值 决定。而当优先级与多个 CSS 声明中任意一个声明的优先级相等的时候,CSS 中最后的那个声明将会被应用到元素上。当同一个元素有多个声明的时候,优先级才会有意义。因为每一个直接作用于元素的 CSS

    2020/03/20
  • JS变量存储与深拷贝和浅拷贝入门基础教程_拷贝零基础入门

    变量类型与存储空间栈内存和堆内存基本数据类型string、number、null、undefined、boolean、symbol(ES6新增) 变量值存放在栈内存中,可直接访问和修改变量的值基本数据类型不存在拷贝,好比如说你无法修改数值1的值引用类型Object Function RegExp Math Date 值为对象,存放在堆内存中在栈内存中变量保存

    2020/03/20
  • node适合用于什么项目?小白基础_项目指南教程

    Node是一个基于Chrome JavaScript运行时建立的平台, 可以方便地搭建响应速度快、易于扩展的网络应用。Node 使用事件驱动, 非阻塞I/O 模型而得以轻量和高效,非常适合在分布式设备上运行数据密集型的实时应用。首先,node新开一个http连接的开销,相当于一个大函数调用,相比php的新开线程动辄花费2MB内存和上下文切换的漫长时间,已经很

    2020/03/22
  • Js map、reduce、filter 等高阶函数基础知识_函数入门基础

    高阶函数高阶函数是对其他函数进行操作的函数,可以将它们作为参数或通过返回它们。简单来说,高阶函数是一个函数,它接收函数作为参数或将函数作为输出返回。例如Array.prototype.map,Array.prototype.filter并且Array.prototype.reduce是一些高阶功能,内置的语言。1. Array.prototype.map该m

    2020/03/23
  • 成为一个优秀架构师,你必须了解的 30 条设计原则菜鸟教程_架构入门教程

    众所周知,架构师的角色,更偏向于策划、而非指挥,塑造、而非支配,其存在的意义,在于引导大家讨论、而非自己主宰一切。但是,具体应该如何执行呢?本文作者整理了 30 个公认的架构原则,来帮助大家解决此问题。也许有的原则,你从未听说,但你看完就能快速学会。相信你学会了,工作起来也会事半功倍,或许还可帮你避免很多无用的加班!本文作者叫 Srinath Perera,

    2020/03/24
  • 区块链挖矿原来是这么一回事入门基础教程_区块链入门攻略

    在整个数字货币领域,很多人都知道挖矿,然而,有些人认为挖矿很赚钱,也有人认为挖矿就是个坑。但是对于挖矿而言,其本质是一种加密的计算。什么是挖矿?简单说,挖矿其实就是数字货币发行的一个过程,通过区块链技术对链上数据进行记录,然后进行广播从而获得奖励,那么这个奖励就是新发行的数字货币。目前,数字货币的发行主要分为两种: 一种是通过计算机运行特定的算法争夺记账权益

    2020/03/23
  • DeepLearning-500-questions小白攻略_深度学习500问

    DeepLearning-500-questions小白攻略 GitHub:https://github.com/scutan90/DeepLearning-500-questio…

    2020/03/06
  • vue props传值常见问题使用攻略_props零基础入门

    传入的值想作为局部变量来使用,直接使用会 报错。错误是说的避免直接修改父组件传入的值,因为会改变父组件的值解决方案:可以在data中重新定义一个变量,改变指向,但是也只是针对简单数据类型,因为复杂数据类型栈存贮的是指针,props:[‘listShop’],data(){return{listShopChild:this.listShop}},created

    2020/03/20
  • 几个牛X的js开发技巧小白知识_技巧入门教程

    1. 确保数组值使用 grid ,需要重新创建原始数据,并且每行的列长度可能不匹配, 为了确保不匹配行之间的长度相等,可以使用Array.fill方法。let array = Array(5).fill(‘‘);
    console.log(array); // outputs (5) [“”, “”, “”, “”, “”]2. 获取数组唯一值ES6 提供了从

    2020/03/22
  • 解析移动端滚动穿透菜鸟教程_滚动菜鸟教程

    滚动穿透在移动端开发中是一个很常见的问题,产生诡异的交互行为,影响用户体验,同时也让我们的产品看起来不那么“专业”。虽然不少产品选择容忍了这样的行为,但是作为追求极致的工程师,应该去了解为什么会产生以及如何去解决。什么是滚动穿透移动端开发中避免不了会在页面上进行弹窗、加浮层等这种操作。一个最常见的场景就是整个页面上有一个遮罩层,上面画着各种各样的东西,具体是

    2020/03/24