從一道面試題談談函數柯里化(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中留言打賞,願你們一同進步。
  此處輸入圖片的描述  

相關文章
相關標籤/搜索