高階函數(shù)除了可以接受函數(shù)作為參數(shù)外,還可以把函數(shù)作為結果值返回坤溃。
function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum;
}
- 當我們調(diào)用lazy_sum()時拍霜,返回的并不是求和結果,而是求和函數(shù):
var f = lazy_sum([1, 2, 3, 4, 5]); // function sum() - 調(diào)用函數(shù)f()時薪介,才真正計算求和的結果:
當lazy_sum返回函數(shù)sum時祠饺,相關參數(shù)和變量都保存在返回的函數(shù)中,這種稱為“閉包(Closure)”的程序結構擁有極大的威力汁政。
請再注意一點道偷,當我們調(diào)用lazy_sum()時,每次調(diào)用都會返回一個新的函數(shù)
var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false
f1()和f2()的調(diào)用結果互不影響。
另一個需要注意的問題是挪蹭,返回的函數(shù)并沒有立刻執(zhí)行讽坏,而是直到調(diào)用了f()才執(zhí)行。我們來看一個例子:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
在上面的例子中换途,每次循環(huán),都創(chuàng)建了一個新的函數(shù)刽射,然后军拟,把創(chuàng)建的3個函數(shù)都添加到一個Array中返回了。
你可能認為調(diào)用f1()誓禁,f2()和f3()結果應該是1懈息,4,9摹恰,但實際結果是:
f1(); // 16
f2(); // 16
f3(); // 16```
原因就在于返回的函數(shù)引用了變量i辫继,但它并非立刻執(zhí)行怒见。等到3個函數(shù)都返回時,它們所引用的變量i已經(jīng)變成了4姑宽,因此最終結果為16遣耍。
`返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會發(fā)生變化的變量低千。`
如果一定要引用循環(huán)變量怎么辦配阵?方法是再創(chuàng)建一個函數(shù),用該函數(shù)的參數(shù)綁定循環(huán)變量當前的值示血,無論該循環(huán)變量后續(xù)如何更改棋傍,已綁定到函數(shù)參數(shù)的值不變:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); // 1
f2(); // 4
f3(); // 9
注意這里用了一個“創(chuàng)建一個匿名函數(shù)并立刻執(zhí)行”的語法:
(function (x) {
return x * x;
})(3); // 9
理論上講,創(chuàng)建一個匿名函數(shù)并立刻執(zhí)行可以這么寫:
function (x) { return x * x } (3);
但是由于JavaScript語法解析的問題难审,會報SyntaxError錯誤瘫拣,因此需要用括號把整個函數(shù)定義括起來:
(function (x) { return x * x }) (3);
通常,一個立即執(zhí)行的匿名函數(shù)可以把函數(shù)體拆開告喊,一般這么寫:
(function (x) {
return x * x;
})(3);
閉包有非常強大的功能麸拄。舉個栗子:
在面向?qū)ο蟮某绦蛟O計語言里,比如Java和C++黔姜,要在對象內(nèi)部封裝一個私有變量拢切,可以用private修飾一個成員變量。
在沒有class機制秆吵,只有函數(shù)的語言里淮椰,借助閉包,同樣可以封裝一個私有變量纳寂。我們用JavaScript創(chuàng)建一個計數(shù)器:
'use strict';
function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
它用起來像這樣:
var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3
var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
在返回的對象中主穗,實現(xiàn)了一個閉包,該閉包攜帶了局部變量x毙芜,并且忽媒,從外部代碼根本無法訪問到變量x。換句話說腋粥,閉包就是攜帶狀態(tài)的函數(shù)晦雨,并且它的狀態(tài)可以完全對外隱藏起來。
閉包還可以把多參數(shù)的函數(shù)變成單參數(shù)的函數(shù)隘冲。例如金赦,要計算xy可以用Math.pow(x, y)函數(shù),不過考慮到經(jīng)常計算x2或x3对嚼,我們可以利用閉包創(chuàng)建新的函數(shù)pow2和pow3:
function make_pow(n) {
return function (x) {
return Math.pow(x, n);
}
}
// 創(chuàng)建兩個新函數(shù):
var pow2 = make_pow(2);
var pow3 = make_pow(3);
pow2(5); // 25
pow3(7); // 343