JS函数柯里化的作用和特点是什么,怎么进行封装
Admin 2022-07-05 群英技术资讯 470 次浏览
在计算机科学中,柯里化(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可以继续柯里化。就是一个套娃的过程~
那么费这么大劲将函数柯里化有什么用呢?
工作中会遇到的需求:通过正则校验电话号、邮箱、身份证是否合法等等
于是我们会封装一个校验函数如下:
/** * @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
)解决了兼容性问题,但也会带来使用的不便,比如不同的应用场景需要传递多个不同的参数来解决问题
有的时候同一种规则可能会反复使用(比如校验手机的参数),这就造成了代码的重复,利用柯里化就能够消除重复,达到复用参数的目的。
柯里化的一种重要思想:降低适用范围,提高适用性
在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) } } }
当然没必要这么改~
事实上,上述正则校验和事件监听的例子中已经体现了延迟执行。
curryingCheckByRegExp
函数调用后返回了checkPhone
和checkEmail
函数
curringAddEvent
函数调用后返回了addEvent
函数
返回的函数都不会立即执行,而是等待调用。
上面我们对函数进行柯里化都是手动修改了原函数,将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+)+)$/))
但是上面事件监听的例子就不能用这个工具函数进行柯里化了,原因前面说了,因为它的条件直接从全局获取了,所以比较特殊,改成从外部传入条件,就能用工具函数柯里化了。当然没这个必要,直接修改原函数更直接、可读性更强
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要介绍vue中setup函数的使用,对于setup函数的特性和、作用和需要注意的点本文都有详细的介绍,另外还分享了setup函数使用的两种方法的实例,感兴趣的朋友可以参考,接下来我们一起了解看看吧。
这篇文章主要介绍了vscode中console.log的两种快速写法,每种方法通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
JavaScript 不提供任何内存管理操作。相反,内存由 JavaScript VM 通过内存回收过程管理,该过程称为垃圾收集。
这篇文章主要给大家分享的是JavaScript 的反射学习技巧,主要是区别在于所有的函数对象属性过于复杂,而且额外增加可能会导致程序行为不合理,所以扩展 Reflect 函数来专门对函数对象处理调用方法,构造对象,获取或者设置属性等相关操作。下面一起进入文章内容吧
这篇文章主要介绍了JavaScript WeakMap使用的详细介绍,帮助大家更好的理解和使用JavaScript,感兴趣的朋友可以了解下
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008