最近在整理面试资源的时候,发现一道有意思的题目,所以就记录下来。

题目

如何实现 multi(2)(3)(4)=24?

首先来分析下这道题,实现一个 multi 函数并依次传入参数执行,得到最终的结果。通过题目很容易得到的结论是,把传入的参数相乘就能够得到需要的结果,也就是 2X3X4 = 24。

简单的实现

那么如何实现 multi 函数去计算出结果值呢?脑海中首先浮现的解决方案是,闭包。

function multi(a) {return function(b) {return function(c) {return a * b * c;}}
}

利用闭包的原则,multi 函数执行的时候,返回 multi 函数中的内部函数,再次执行的时候其实执行的是这个内部函数,这个内部函数中接着又嵌套了一个内部函数,用于计算最终结果并返回。


单纯从题面来说,似乎是已经实现了想要的结果,但仔细一想就会发现存在问题。

上面的实现方案存在的缺陷:

  • 代码不够优雅,实现步骤需要一层一层的嵌套函数。
  • 可扩展性差,假如是要实现 multi(2)(3)(4)...(n) 这样的功能,那就得嵌套 n 层函数。

那么有没有更好的解决方案,答案是,使用函数式编程中的函数柯里化实现。

函数柯里化

在函数式编程中,函数是一等公民。那么函数柯里化是怎样的呢?

函数柯里化指的是将能够接收多个参数的函数转化为接收单一参数的函数,并且返回接收余下参数且返回结果的新函数的技术。

函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。

例如:封装兼容现代浏览器和 IE 浏览器的事件监听的方法,正常情况下封装是这样的。

var addEvent = function(el, type, fn, capture) {if(window.addEventListener) {el.addEventListener(type, function(e) {fn.call(el, e);}, capture);}else {el.attachEvent('on'   type, function(e) {fn.call(el, e);})}
}

该封装的方法存在的不足是,每次写监听事件的时候调用 addEvent 函数,都会进行 if else 的兼容性判断。事实上在代码中只需要执行一次兼容性判断就可以了,后续的事件监听就不需要再去判断兼容性了。那么怎么用函数柯里化优化这个封装函数。

var addEvent = (function() {if(window.addEventListener) {return function(el, type, fn, capture) {el.addEventListener(type, function(e) {fn.call(el, e);}, capture);}}else {return function(ele, type, fn) {el.attachEvent('on'   type, function(e) {fn.call(el, e);})}}
})()

js 引擎在执行该段代码的时候就会进行兼容性判断,并且返回需要使用的事件监听封装函数。这里使用了函数柯里化的两个特点:提前返回和延迟执行。

柯里化另一个典型的应用场景就是 bind 函数的实现。使用了函数柯里化的两个特点:参数复用和提前返回。

Function.prototype.bind = function(){var fn = this;var args = Array.prototye.slice.call(arguments);var context = args.shift();return function(){return fn.apply(context, args.concat(Array.prototype.slice.call(arguments)));};
};

柯里化的实现

那么如何通过函数柯里化实现面试题的功能呢?

通用版

function curry(fn) {var args = Array.prototype.slice.call(arguments, 1);return function() {var newArgs = args.concat(Array.prototype.slice.call(arguments));return fn.apply(this, newArgs);}
}

curry 函数的第一个参数是要动态创建柯里化的函数,余下的参数存储在 args 变量中。

执行 curry 函数返回的函数接收新的参数与 args 变量存储的参数合并,并把合并的参数传入给柯里化了的函数。

function multiFn(a, b, c) {return a * b * c;
}
var multi = curry(multiFn);
multi(2,3,4);

结果:


虽然得到的结果是一样的,但是很容易发现存在问题,就是代码相对于之前的闭包实现方式较复杂,而且执行方式也不是题目要求的那样 multi(2)(3)(4)。那么下面就来改进这版代码。

改进版

就题目而言,是需要执行三次函数调用,那么针对柯里化后的函数,如果传入的参数没有 3 个的话,就继续执行 curry 函数接收参数,如果参数达到 3 个,就执行柯里化了的函数。

function curry(fn, args) {var length = fn.length;var args = args || [];return function(){newArgs = args.concat(Array.prototype.slice.call(arguments));if(newArgs.length < length){return curry.call(this,fn,newArgs);}else{return fn.apply(this,newArgs);}}
}
function multiFn(a, b, c) {return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);

可以看到,通过改进版的柯里化函数,已经将题目定的实现方式扩展到好几种了。这种实现方案的代码扩展性就比较强了,但是还是有点不足,就是必须事先知道求值的参数个数,那能不能让代码更灵活点,达到随意传参的效果,例如: multi(2)(3)(4),multi(5)(6)(7)(8)(9) 这样的。

优化版

function multi() {var args = Array.prototype.slice.call(arguments);var fn = function() {var newArgs = args.concat(Array.prototype.slice.call(arguments));return multi.apply(this, newArgs);}fn.toString = function() {return args.reduce(function(a, b) {return a * b;})}return fn;
}

这样的解决方案就可以灵活的使用了。不足的是返回值是 Function 类型。


总结

  • 就题目本身而言,是存在多种实现方式的,只要理解并充分利用闭包的强大。
  • 可能在实际应用场景中,很少使用函数柯里化的解决方案,但是了解认识函数柯里化对自身的提升还是有帮助的。
  • 理解闭包和函数柯里化之后,如果在面试中遇到类似的题型,应该就可以迎刃而解了。

后记

本着学习和总结的态度写的技术输出,文中有任何错误和问题,请大家指出。更多的技术输出可以查看我的 github博客。

整理了一些前端的学习资源,希望能够帮助到有需要的人,地址: 学习资源汇总。

参考

  • https://segmentfault.com/q/1010000004014052
  • https://blog.csdn.net/crystal6918/article/details/77141741
  • https://mp.weixin.qq.com/s?__biz=MjM5MTA1MjAxMQ==&mid=2651228431&idx=1&sn=c9d62a30a52f4572cc0cb4aaf2a82ef3

从一道面试题认识函数柯里化相关推荐

  1. 高级函数技巧-函数柯里化

    我们经常说在Javascript语言中,函数是"一等公民",它们本质上是十分简单和过程化的.可以利用函数,进行一些简单的数据处理,return 结果,或者有一些额外的功能,需要通过 ...

  2. JS - 函数柯里化

    一.概念 柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数. 简单来说,柯里化是一种函数的转换,它是指将一个函数从可调用的 ...

  3. 高阶函数、js函数内返回一个内部函数详解---->函数柯里化

    高阶函数 如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数. 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数. 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函 ...

  4. js面试高频题:函数柯里化的实现(彻底弄懂)

    函数柯里化的适用场景有: 1. 参数复用 2. 延时执行 3. 提前确认 函数柯里化的核心在于:函数里面返回函数,从而做到参数复用的目的. 我们以一个js经典面试题为例开始讲解: 实现一个函数,使得满 ...

  5. 函数柯里化的意义_详解JS函数柯里化

    第一次看到柯里化这个词的时候,还是在看一篇算法相关的博客提到把函数柯里化,那时一看这个词就感觉很高端,实际上当你了解了后才发现其实就是高阶函数的一个特殊用法. 果然是不管作用怎么样都要有个高端的名字才 ...

  6. 带你看懂javascript函数柯里化(currying)

    1.什么是柯里化 这里参照百度百科: 在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技 ...

  7. 什么是函数柯里化,函数柯里化的应用场景,函数柯里化的优缺点

    函数柯里化 1. 什么是函数柯里化? 2. 函数柯里化面试题 3. 应用场景 1. 什么是函数柯里化? 函数柯里化是js闭包的典型应用.所以缺点就是闭包造成的缺点,占用内存较多等 什么是函数柯里化?就 ...

  8. 打造属于自己的underscore系列(五)- 偏函数和函数柯里化

    这一节的内容,主要针对javascript函数式编程的两个重要概念,偏函数(partial application) 和函数柯里化(curry)进行介绍.着重讲解underscore中对于偏函数应用的 ...

  9. reactjs高阶函数和函数柯里化

    高阶函数.函数柯里化 <!DOCTYPE html> <html lang="en"> <head><meta charset=" ...

最新文章

  1. Linux命令行与shell脚本编程大全:第2版
  2. [转]使用设计模式改善程序结构(二)
  3. C++ Primer笔记12_运算符重载_递增递减运算符_成员訪问运算符
  4. c++ocx交互检测弹框_吉利几何C:2022杭州亚运会移动“明信片”!
  5. 【Android 逆向】Android 进程注入工具开发 ( 注入代码分析 | 获取 linker 中的 dlopen 函数地址 并 通过 远程调用 执行该函数 )
  6. Spring的@ImportResource注解
  7. 【Linux】一步一步学Linux——ss命令(170)
  8. java rpg对战_箭头键优先(java rpg游戏)
  9. 迭代器(Iterator)遍历的两种方法(for和while)
  10. background意识(两)
  11. matplotlib 直方图_Matplotlib直方图和散点图
  12. JWT认证不通过导致不能访问视图的解决方案
  13. 医院管理系统/医院药品管理系统
  14. app测试和web测试的区别
  15. 魔兽服务器人口查询网站是多少,网易魔兽世界人口普查
  16. zookeeper学习一-ZK简介
  17. Redis从入门到入坟系列文章(一): keys 命令
  18. 移动端 html 表单案例,【干货】5大移动端表单设计原则及案例赏析
  19. Airsim 无人机仿真
  20. 春天开始Spring

热门文章

  1. python求最小公倍数
  2. mysql火焰图_【性能】如何使用perf和火焰图分析系统性能?
  3. PyTorch第三章
  4. PCB设计--AD18导入二维码避坑指南
  5. 学校计算机的使用英语作文180字,英文作文学生开车去学校,180字
  6. python微信公众号翻译功能怎么用_使用python一步一步搭建微信公众平台(二)----搭建一个中英互译的翻译工具...
  7. 学期计算机教学工作反思,小学信息技术老师一月工作反思总结教育教学笔记
  8. php 细表格,使用PHP轻松地创建一个表格 - 小俊学习网
  9. java换算当地时间_Java UTC时间与本地时间互相转换
  10. PMP®第五章:项目范围管理