JavaScript函数柯里化

目录
  • 1 什么是函数柯里化
  • 2 柯里化的作用和特点
    • 2.1 参数复用
    • 2.2 提前返回
    • 2.3 延迟执行
  • 3 封装通用柯里化工具函数#
  • 4 总结和补充

1 什么是函数柯里化

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术以逻辑学家 Haskell Curry 命名的。

什么意思?简单来说,柯里化是一项技术,它用来改造多参数的函数。

比如:

// 这是一个接受3个参数的函数
const add = function(x, y, z) {
  return x + y + z
}

我们将它变换一下,可以得到这样一个函数:

// 接收一个单一参数
const curryingAdd = function(x) {
  // 并且返回接受余下的参数的函数
  return function(y, z) {
    return x + y + z
  }
}

这样有什么区别呢?从调用上来对比:

// 调用add
add(1, 2, 3)

// 调用curryingAdd
curryingAdd(1)(2, 3)
// 看得更清楚一点,等价于下面
const fn = curryingAdd(1)
fn(2, 3)

可以看到,变换后的的函数可以分批次接受参数,先记住这一点,下面会讲用处。甚至fn(curryingAdd返回的函数)还可以继续变换

如下:

const curryingAdd = function(x) {
  return function(y) {
    return function(z) {
      return x + y + z
    }
  }
}
// 调用
curryingAdd(1)(2)(3)
// 即
const fn = curryingAdd(1)
const fn1 = fn(2)
fn1(3)

上面的两次变换过程,就是函数柯里化。

简单讲就是把一个多参数的函数f,变换成接受部分参数的函数g,并且这个函数g会返回一个函数h,函数h用来接受其他参数。函数h可以继续柯里化。就是一个套娃的过程~

那么费这么大劲将函数柯里化有什么用呢?

2 柯里化的作用和特点

2.1 参数复用

工作中会遇到的需求:通过正则校验电话号、邮箱、身份证是否合法等等

于是我们会封装一个校验函数如下:

/**
 * @description 通过正则校验字符串
 * @param {RegExp} regExp 正则对象
 * @param {String} str 待校验字符串
 * @return {Boolean} 是否通过校验
 */
function checkByRegExp(regExp, str) {
    return regExp.test(str)
}

假如我们要校验很多手机号、邮箱,我们就会这样调用:

// 校验手机号
checkByRegExp(/^1\d{10}$/, '15152525634');
checkByRegExp(/^1\d{10}$/, '13456574566');
checkByRegExp(/^1\d{10}$/, '18123787385');
// 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'fsds@163.com');
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'fdsf@qq.com');
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'fjks@qq.com');

貌似没什么问题,事实上还有改进的空间

  • 校验同一类型的数据时,相同的正则我们写了很多次。
  • 代码可读性较差,如果没有注释,我们并不能一下就看出来正则的作用

我们试着使用函数柯里化来改进:

// 将函数柯里化
function checkByRegExp(regExp) {
    return function(str) {
        return regExp.test(str)
    }
}

于是我们传入不同的正则对象,就可以得到功能不同的函数:

// 校验手机
const checkPhone = curryingCheckByRegExp(/^1\d{10}$/)
// 校验邮箱
const checkEmail = curryingCheckByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/)

现在校验手机、邮箱的代码就简单了,并且可读性也增强了

// 校验手机号
checkPhone('15152525634');
checkPhone('13456574566');
checkPhone('18123787385');
// 校验邮箱
checkEmail('fsds@163.com');
checkEmail('fdsf@qq.com');
checkEmail('fjks@qq.com');

这就是参数复用:我们只需将第一个参数regExp复用,就可以直接调用有特定功能的函数

通用函数(如checkByRegExp)解决了兼容性问题,但也会带来使用的不便,比如不同的应用场景需要传递多个不同的参数来解决问题

有的时候同一种规则可能会反复使用(比如校验手机的参数),这就造成了代码的重复,利用柯里化就能够消除重复,达到复用参数的目的。

柯里化的一种重要思想:降低适用范围,提高适用性

2.2 提前返回

JS DOM事件监听程序中,我们用addEventListener方法为元素添加事件处理程序,但是部分浏览器版本不支持此方法,我们会使用attachEvent方法来替代。

这时我们会写一个兼容各浏览器版本的代码:

/**
 * @description:
 * @param {object} element DOM元素对象
 * @param {string} type 事件类型
 * @param {Function} fn 事件处理函数
 * @param {boolean} isCapture 是否捕获
 * @return {void}
 */
function addEvent(element, type, fn, isCapture) {
    if (window.addEventListener) {
        element.addEventListener(type, fn, isCapture)
    } else if (window.attachEvent) {
        element.attachEvent("on" + type, fn)
    }
}

我们用addEvent来添加事件监听,但是每次调用此方法时,都会进行一次判断,事实上浏览器版本确定下来后,没有必要进行重复判断。

柯里化处理:

function curryingAddEvent() {
    if (window.addEventListener) {
        return function(element, type, fn, isCapture) {
            element.addEventListener(type, fn, isCapture)
        }
    } else if (window.attachEvent) {
        return function(element, type, fn) {
            element.attachEvent("on" + type, fn)
        }
    }
}
const addEvent = curryingAddEvent()

// 也可以用立即执行函数将上述代码合并
const addEvent = (function curryingAddEvent() {
  ...
})()

现在我们得到的addEvent是经过判断后得到的函数,以后调用就不用重复判断了。

这就是提前返回或者说提前确认,函数柯里化后可以提前处理部分任务,返回一个函数处理其他任务

另外,我们可以看到,curryingAddEvent好像并没有接受参数。这是因为原函数的条件(即浏览器的版本是否支持addEventListener)是直接从全局获取的。

逻辑上其实是可以改成:

let mode = window.addEventListener ? 0 : 1;
function addEvent(mode, element, type, fn, isCapture) {
  if (mode === 0) {
    element.addEventListener(type, fn, isCapture);
  } else if (mode === 1) {
    element.attachEvent("on" + type, fn);
  }
}
// 这样柯里化后就可以先接受一个参数了
function curryingAddEvent(mode) {
    if (mode === 0) {
        return function(element, type, fn, isCapture) {
            element.addEventListener(type, fn, isCapture)
        }
    } else if (mode === 1) {
        return function(element, type, fn) {
            element.attachEvent("on" + type, fn)
        }
    }
}

当然没必要这么改~

2.3 延迟执行

事实上,上述正则校验和事件监听的例子中已经体现了延迟执行。

curryingCheckByRegExp函数调用后返回了checkPhonecheckEmail函数

curringAddEvent函数调用后返回了addEvent函数

返回的函数都不会立即执行,而是等待调用。

3 封装通用柯里化工具函数#

上面我们对函数进行柯里化都是手动修改了原函数,将add改成了curryingAdd、将checkByRegExp改成了curryingCheckByRegExp、将addEvent改成了curryingAddEvent

难道我们每次对函数进行柯里化都要手动修改底层函数吗?当然不是

我们可以封装一个通用柯里化工具函数(面试手写代码)

/**
 * @description: 将函数柯里化的工具函数
 * @param {Function} fn 待柯里化的函数
 * @param {array} args 已经接收的参数列表
 * @return {Function}
 */
const currying = function(fn, ...args) {
    // fn需要的参数个数
    const len = fn.length
    // 返回一个函数接收剩余参数
    return function (...params) {
        // 拼接已经接收和新接收的参数列表
        let _args = [...args, ...params]
        // 如果已经接收的参数个数还不够,继续返回一个新函数接收剩余参数
        if (_args.length < len) {
            return currying.call(this, fn, ..._args)
        }
       // 参数全部接收完调用原函数
        return fn.apply(this, _args)
    }
}

这个柯里化工具函数用来接收部分参数,然后返回一个新函数等待接收剩余参数,递归直到接收到全部所需参数,然后通过apply调用原函数。

现在我们基本不用手动修改原函数来将函数柯里化了

// 直接用工具函数返回校验手机、邮箱的函数
const checkPhone = currying(checkByRegExp(/^1\d{10}$/))
const checkEmail = currying(checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/))

但是上面事件监听的例子就不能用这个工具函数进行柯里化了,原因前面说了,因为它的条件直接从全局获取了,所以比较特殊,改成从外部传入条件,就能用工具函数柯里化了。当然没这个必要,直接修改原函数更直接、可读性更强

4 总结和补充

  • 柯里化突出一种重要思想:降低适用范围,提高适用性
  • 柯里化的三个作用和特点:参数复用、提前返回、延迟执行
  • 柯里化是闭包的一个典型应用,利用闭包形成了一个保存在内存中的作用域,把接收到的部分参数保存在这个作用域中,等待后续使用。并且返回一个新函数接收剩余参数

到此这篇关于JavaScript函数柯里化的文章就介绍到这了,更多相关函数柯里化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript函数柯里化原理与用法分析

    本文实例讲述了JavaScript函数柯里化原理与用法.分享给大家供大家参考,具体如下: 柯里化是这样的一个转换过程,把接受多个参数的函数变换成接受一个单一参数(译注:最初函数的第一个参数)的函数,如果其他的参数是必要的,返回接受余下的参数且返回结果的新函数. 也就是说是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数. 例如,我想创建一个做自我介绍的函数,每个人只要输入自己姓名.性别.年龄即可.但是当A使用这个函数时,每次调用,都必

  • 浅谈JS中的bind方法与函数柯里化

    绑定函数bind()最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的this值.不同于call和apply只是单纯地设置this的值后传参,它还会将所有传入bind()方法中的实参(第一个参数之后的参数)与this一起绑定. 关于这个特性看<JS权威指南>原文的例子: var sum = function(x,y) { return x + y }; var succ = sum.bind(null, 1); //让this指向null,其后的实参也会作为实参传入被绑定的函数sum

  • Javascript闭包与函数柯里化浅析

    闭包和柯里化都是JavaScript经常用到而且比较高级的技巧,所有的函数式编程语言都支持这两个概念,因此,我们想要充分发挥出JavaScript中的函数式编程特征,就需要深入的了解这两个概念,闭包事实上更是柯里化所不可缺少的基础. 一.柯里化的概念 在计算机科学中,柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术.这个技术由Christopher Strachey以逻辑学家 Haskell Curry 命名的,尽管

  • js函数柯里化的方法和作用实例分析

    本文实例讲述了js函数柯里化的方法和作用.分享给大家供大家参考,具体如下: 函数柯里化的方法: 1,利用数组的reduce方法可实现柯里化,具体参见我的另外一篇文章 2,代码如下 const curry = (fn, ...args) =>{ console.log('args',args) // console.log(fn.length,args.length) return args.length < fn.length // 参数长度不足时,重新柯里化该函数,等待接受新参数 ? (..

  • 前端JavaScript彻底弄懂函数柯里化curry

    目录 一.什么是柯里化( curry) 二.柯里化的用途 三.如何封装柯里化工具函数 一.什么是柯里化( curry) 在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术. 举例来说,一个接收3个参数的普通函数,在进行柯里化后, 柯里化版本的函数接收一个参数并返回接收下一个参数的函数, 该函数返回一个接收第三个参数的函数. 最后一个函数在接收第三个参数后, 将之前接收到的三个参数应用于原普通函数中,并返回最终结果. 数学和计算科学中的柯里化: // 数

  • JavaScript函数柯里化实现原理及过程

    简介 当我们在读Vue源码到时候会发现,在它的 _update 实例中就用到了函数柯里化,(createPatchFunction方法)有兴趣的可以 去看一下. 柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术. 在<Mostly adequate guide>中,这样总结了 Currying --只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数. Currying 是函数式编

  • 怎样用Javascript实现函数柯里化与反柯里化

    函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感:下面来一起看看究竟什么是函数柯里化: 维基百科的解释是:把接收多个参数的函数变换成接收一个单一参数(最初函数的第一个参数)的函数,并返回接受剩余的参数而且返回结果的新函数的技术.其由数学家Haskell Brooks Curry提出,并以curry命名. 概念往往都是干涩且难懂的,让我们用人话来解释就是:如果我们不确定这个函数有多少个参数,我们可以先给它传入一个参数,然后通过JS闭包(如若不懂JS闭包,请先学

  • javascript实现函数柯里化与反柯里化过程解析

    函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感:下面来一起看看究竟什么是函数柯里化: 维基百科的解释是:把接收多个参数的函数变换成接收一个单一参数(最初函数的第一个参数)的函数,并返回接受剩余的参数而且返回结果的新函数的技术.其由数学家Haskell Brooks Curry提出,并以curry命名. 概念往往都是干涩且难懂的,让我们用人话来解释就是:如果我们不确定这个函数有多少个参数,我们可以先给它传入一个参数,然后通过JS闭包(如若不懂JS闭包,请先学

  • JavaScript函数柯里化详解

    什么是柯里化 柯里化是这样的一个转换过程,把接受多个参数的函数变换成接受一个单一参数(译注:最初函数的第一个参数)的函数,如果其他的参数是必要的,返回接受余下的参数且返回结果的新函数. 柯理化函数思想:一个js预先处理的思想:利用函数执行可以形成一个不销毁的作用域的原理,把需要预先处理的内容都储存在这个不销毁的作用域中,并且返回一个小函数,以后我们执行的都是小函数,在小函数中把之前预先存储的值进行相关的操作处理即可: 柯里化函数主要起到预处理的作用: bind方法的作用:把传递进来的callba

  • 浅析JavaScript 函数柯里化

    柯里化 (Currying)是把接收多个参数的原函数变换成接受一个单一参数(原来函数的第一个参数的函数)并返回一个新的函数,新的函数能够接受余下的参数,并返回和原函数相同的结果. ES6的方式实现柯里化的通用 function currying(fn,...rest1){ return function(...rest2){ //这里用apply 是为把数组形式的参数直接传入原函数 null是因为不需要改变this return fn.apply(null,rest1.concat(rest2)

随机推荐