javascript閉包和柯里化的深度解釋

<br />javascript擁有簡潔的表達,使你可以專心于算法攻略。就好像黑白機上的闖關游戲,你拾取了寶劍,只需要不停地點A就可以了。你唯一要思考的就是如何不停地跳躲Boss的大招。

javascript成為瀏覽器的唯一語言,并且成為世界標準許多年,是有非常重要的理由的。《JavaScript: The Good Parts》做出了非常清晰地解釋。

JavaScript:The World's Most Misunderstood Programming Language

然而,想要掌握javascript的正確編寫方式并不容易。尤其是當你從教科書開始的時候,大部分給你的信息都是面向對象的東西:newprototypeclassextend,...

這些都不是正確編寫javascript的方式,當然你可以這么做,但是你會恨上javascript,這樣編寫的感覺會讓你覺得你在寫java卻不能擁有java一樣的計算速度。

想要喜歡上javascript并且享受programming的快感,你需要放下一切對面向對象的理解,走進函數和求值的世界。你的所有代碼只有function,這是簡單的并且靈活的。
<br />


什么是函數?

<br />如同數學界,函數function即代表了求值。然而,從另一個角度來講,函數function也是值:

g = f(x)

我們認為g是一個值,來自f函數的計算,輸入是x。然而,我們可以把值g作為一個函數,輸入y,再次進行求值:

v = g(y)

把函數function賦予值的定位,可以使計算充滿了靈活:

v = f(x)(y) = k(f, x, y)

<br />


什么是柯里化Currying?

<br />f(x)(y)就是柯里化:使用函數f,輸入x,計算,獲得一個新的函數,再次輸入y,計算,獲取結果。f(x)(y)(z)(a)(b)(c),你完全可以寫這樣的函數。每次進行一次計算時,都返回一個新的函數。當然,你也可以寫成這樣的方式g(x, y, z, a, b, c)

function f (x) {
  return function (y) {
    return function (z) {
      return function (a) {
        return function (b) {
          return function (c) {
              console.log(x  + y + z + a + b + c);
          };
        };
      };
    };
  };
}

計算6個數字相加為什么要費這么大的周折?原因在于可以獲得擁有內部記憶的函數: g = f(1)可以獲得一個新的函數g。可以使用g編寫多種不同的求值組合,而使第一個輸入始終是1:

g(2)(3)(4)(5)(6);
g(3)(3)(4)(5)(6);
g(9)(9)(1)(2)(3);

<br />


什么是閉包Closure?

<br />上邊的函數就是閉包,確切的說,利用柯里化機制的函數function就是閉包函數。通過柯里化,獲取一個擁有記憶功能的函數,簡化后續的多種計算操作,這就是閉包。

function move(start) {
  var pos = start;
  return function () {
    console.log('Move to ' + (pos += 2) + '.');
  }
}

var move_next = move(6); 
move_next();  // Move to 8. 
move_next();  // Move to 10.

<br />


進階:記憶

<br />下面我們來看一下經典的緩存函數。開始有兩個輸入參數,一個是數組sets,一個是求值函數f

function memorize(sets, f) {
    var cache = {}; 
    return function (x) { 
        console.log('cache: %j', cache);
        return x in cache
               ? cache[x]
               : cache[x] = f(sets[x]);
    }
}

首先,我們在memorize的內部空間放置了一個記憶單元:cache,是一個Object類型,這樣我們就可以用來存儲任何我們想要的數據。Object類型可以看做是簡化并且統一版的C語言中的struct:不需要考慮鏈接,不需要考慮類型,解釋器會為你完成。

接下來,我們運用柯里化返回一個函數。這個函數有一個輸入參數,指定了sets數組中的第幾項進行計算。我們首先使用console.log打印當時內部空間的記憶單元cache的值,然后判斷輸入參數是不是在cache的鍵中。如果已經存在,直接返回記憶的內容,如果沒有存在,使用函數f對輸入參數sets[x]求值,然后把結果記憶到內部空間的記憶單元:cache中。

通過記憶,每次使用求值函數f計算后,都將結果保存在cache中,這樣可以極大的降低重復計算:

var g = memorize([1000, 2000, 3000], function (x) { return x * x; });
g(0);  // cache: {},計算1000*1000
g(0);  // cache: {"0":1000000},來自記憶
g(0);  // cache: {"0":1000000},來自記憶
g(0);  // cache: {"0":1000000},來自記憶
g(1);  // cache: {"0":1000000},計算2000*2000
g(1);  // cache: {"0":1000000, "1":4000000},來自記憶
g(1);  // cache: {"0":1000000, "1":4000000},來自記憶

<br />


進階:讓函數循環起來

<br />我們已經看到了柯里化(閉包)的好處和妙處,同時這些也都是函數function概念幫助我們完成了一系列繁瑣的工作。下面我們將把函數function運用到循環中,進一步了解函數的好處和妙處。

function map(sets, f) {
  var i = 0, len = sets.length, result = [], val;  
  while (i < len) {
    val = f(sets[i]);
    result.push(val);
    ++i;
  }
  return result;
}

函數map使用兩個輸入參數:一個數組sets,一個求值函數f

首先,我們計算數組sets的長度,設定一個位置符i。然后對sets進行循環的操作,把其中的sets[i]進行求值,然后壓入result中,最后將result返回。

通過這樣的設定,我們使函數f在循環中運轉。我們還可以進一步,再放入一個條件函數,只有條件成功的時候才進行求值:

function map(sets, condf, f) {
  var i = 0, len = sets.length, result = [], val, set;  
  while (i < len) {
    set = sets[i];
    if (condf(set)) {
      val = f(set);
      result.push(val);
    }
    ++i;
  }
  return result;
}

上邊我們剛剛討論了柯里化,所以把這個函數改一改,變成柯里化:

function map(sets, f) {
  return function (condf) {
    var i = 0, len = sets.length, result = [], val, set;  
    while (i < len) {
      set = sets[i];
      if (condf(set)) {
        val = f(set);
        result.push(val);
      }
      ++i;
    }
    return result;
  }
}

現在,函數map在循環中進一步增加了靈活性,我們可以這樣方便的使用:

var mymap = map([1,2,3,4,5,6], function (set) { return set + 1; });
mymap(function (set) { return set % 2 === 0 });  // 偶數
mymap(function (set) { return set > 9 });  // 大于9

// 如果你熟悉Ecmascript 6,那么代碼會非常有趣
var mymap = map([1,2,3,4,5,6], set => set + 1);
mymap(set => set % 2 === 0);  // 偶數
mymap(set => set > 9 );  // 大于9

→ 如何玩轉閉包和柯里化

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容