从一道面试题谈谈函数柯里化(Currying)

  欢迎你们再一次来到个人文章专栏:从面试题中咱们能学到什么,各位同行小伙伴是否已经开始了清闲的春节假期呢?在这里提早祝你们鸡年大吉吧~哈哈,以前有人说,学面试题不会有什么长进,其实我以为这个就像是咱们英语考试中的阅读理解,带着问题去看文章反而更有利于本身的学习。
  以前的两篇文章:javascript

都在稀土掘金和Segmentfault都得到了很是多的点击量,没有看的小伙伴们能够点击了解一下,今天为你们带来一道关于闭包和函数的柯里化方面的编程题目,各位小伙伴有没有开始跃跃欲试呢?
  编程题目的要求以下,完成plus函数,经过所有的测试用例。github

'use strict';
function plus(n){
  
}
module.exports = plus

测试用例以下面试

'use strict';
var assert = require('assert')

var plus = require('../lib/assign-4')

describe('闭包应用',function(){
  it('plus(0) === 0',function(){
    assert.equal(0,plus(0).toString())
  })
  it('plus(1)(1)(2)(3)(5) === 12',function(){
    assert.equal(12,plus(1)(1)(2)(3)(5).toString())
  })
  it('plus(1)(4)(2)(3) === 10',function(){
    assert.equal(10,plus(1)(4)(2)(3).toString())
  })
  it('方法引用',function(){
    var plus2 = plus(1)(1)
    assert.equal(12,plus2(1)(4)(2)(3).toString())
  })
})

  实话说刚开始拿到这道题的时候我并无彻底的作出来,可是具体的思路是有的,确定是关于函数的柯里化(Currying)方面的,应该是想考察一下面试者的闭包理解能力.
  那么首先介绍一下什么是函数的柯里化(Currying)。《JavaScript忍者秘籍》一书中,对于柯里化的定义以下:
  编程

在一个函数中首先填充几个参数(而后再返回一个新函数)的技术称为柯里化(Currying。segmentfault

维基百科中关于其定义以下:浏览器

在计算机科学中,柯里化(Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数并且返回结果的新函数的技术。这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的。闭包

  首先咱们举个例子来具体的解释一下以上的概念。
  例如一个最简单的加法函数:app

//函数定义
function add(x,y){
    return x + y;
}
//函数调用
add(3,4);//5

  若是采用柯里化是怎样将接受两个参数的函数变成接受单一参数的函数呢,其实很简单以下:

//函数表达式定义
var add = function(x){
    return function(y){
        return x + y;
    }
};
//函数调用
add(3)(4);

  这样理解起来实际上是不是就很简单了,其实实质利用的就是闭包的概念(你们能够在个人另外一篇文章浅谈JavaScript闭包看一下)。本质上讲柯里化(Currying)只是一个理论模型,柯里化所要表达是:若是你固定某些参数,你将获得接受余下参数的一个函数,因此对于有两个变量的函数y^x,若是固定了y=2,则获得有一个变量的函数2^x。这就是求值策略中的部分求值策略。
  柯里化(Currying)具备:延迟计算、参数复用、动态生成函数的做用。例如若是咱们须要建立一个通用的DOM事件绑定函数,不使用柯里化的写法以下(该示例来自于博客园Tong Zeng):

//第四个参数用来标识是在冒泡阶段仍是在捕获阶段执行函数
var addEvent = function(el,type,fn,capture){
    if (window.addEventListener) {
         el.addEventListener(type, function(e) {
             fn.call(el, e);
         }, capture);
     } else if (window.attachEvent) {
         el.attachEvent("on" + type, function(e) {
             fn.call(el, e);
         });
     } 
}

  可是在使用了柯里化(Currying)的状况下,再也不须要每次添加事件处理都要执行一遍if...else...判断,只须要在浏览器中断定一次就能够了,把根据一次断定以后的结果动态生成新的函数,之后就没必要从新计算。其实在实际使用中使用最多的一个柯里化的例子就是Function.prototype.bind()函数,咱们也一并给出一个较为简单的Function.prototype.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)));
    };
};

  回到咱们的题目自己,其实根据测试用例咱们能够发现,plus函数的要求就是接受单一函数,例如:

plus(1)(4)(2)(3).toString()

可是与柯里化不一样之处在于,柯里化返回的一个新函数。咱们观察其实最后的求值是经过toString函数获得的,那么咱们就很容易想到,咱们能够给返回的函数增长一个toString属性就能够了。我本身写出的答案以下:

/**
 * Created by lei.wang on 2017/1/22.
 */

'use strict';

function plus(num) {
    var adder = function () {
        var _args = [];
        var _adder = function _adder() {
            [].push.apply(_args, [].slice.call(arguments));
            return _adder;
        };

        _adder.toString = function () {
            return _args.reduce(function (a, b) {
                return a + b;
            });
        }

        return _adder;
    }
    return adder()(num);
}

module.exports = plus;

  运行一下,经过所有的测试用例,须要注意的是因为题目的要求运行在严格模式下,因此咱们在_adder函数内部是不能引用arguments.callee,这时咱们采用的方法是给函数表达式中函数自己起名_adder,这样就解决的这个问题。
  再次感谢你们阅读本篇文章,但愿你们能从中或多或少学到一些东西,入行资历甚浅,不足之处请多指教,欢迎你们在去个人博客MrErHu中留言打赏,愿你们一同进步。
  此处输入图片的描述  

相关文章
相关标签/搜索