<br />javascript擁有簡潔的表達,使你可以專心于算法攻略。就好像黑白機上的闖關游戲,你拾取了寶劍,只需要不停地點A就可以了。你唯一要思考的就是如何不停地跳躲Boss的大招。
javascript成為瀏覽器的唯一語言,并且成為世界標準許多年,是有非常重要的理由的。《JavaScript: The Good Parts》做出了非常清晰地解釋。
JavaScript:The World's Most Misunderstood Programming Language
然而,想要掌握javascript的正確編寫方式并不容易。尤其是當你從教科書開始的時候,大部分給你的信息都是面向對象的東西:new
, prototype
,class
,extend
,...
這些都不是正確編寫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