JavaScript中有兩種定義函數的方式:函數聲明和函數表達式。使用函數表達式無須對函數命名,從而實現動態編程,也即匿名函數。有了匿名函數,JavaScript函數有了更強大的用處。
遞歸
遞歸是一種很常見的算法,經典例子就是階乘。也不扯其他的,直接說遞歸的最佳實踐,上代碼:
// 最佳實踐,函數表達式
var factorial = (function f(num) {
if (num <= 1) {
return 1;
} else {
return num * f(num - 1);
}
});
// 缺點:
// factorial存在被修改的可能
// 導致 return num * factorial(num - 1) 報錯
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
// 缺點:
// arguments.callee,規範已經不推薦使用
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
遞歸就是這樣,好多人還在使用arguments.callee的方式,改回函數表達式的方式吧,這纔是最佳實踐。
囉嗦一句,好多人覺得遞歸難寫,其實你將其分爲兩個步驟就會清晰很多了。
邊界條件,通常是if-else。遞歸調用。
按這個模式,找幾個經典的遞歸練練手,就熟悉了。
閉包
很多人經常覺得閉包很複雜,很容易掉到坑裏,其實不然。
那麼閉包是什麼呢?如果一個函數可以訪問另一個函數作用域中的變量,那麼前者就是閉包。由於JavaScript函數可以返回函數,自然,創建閉包的常用方式就是在一個函數內部創建另一個函數!
這並沒有什麼神奇的,在父函數中定義子函數就可以創建閉包,而子函數可以訪問父函數的作用域。
我們通常是因爲被閉包坑了,纔會被閉包嚇到,尤其是面試題裏一堆閉包。
閉包的定義前面提了,如何創建閉包也說了,那麼我們說說閉包的缺陷以及如何解決?
/* 我們通過subFuncs返回函數數組,然後分別調用執行 */
// 返回函數的數組subFuncs,而這些函數對superFunc的變量有引用
// 這就是一個典型的閉包
// 那麼有什麼問題呢?
// 當我們回頭執行subFuncs中的函數的時候,我們得到的i其實一直都是10,爲什麼?
// 因爲當我們返回subFuncs之後,superFunc中的i=10
// 所以當執行subFuncs中的函數的時候,輸出i都爲10。
//
// 以上,就是閉包最大的坑,一句話理解就是:
// 子函數對父函數變量的引用,是父函數運行結束之後的變量的狀態
function superFunc() {
var subFuncs = new Array();
for (var i = 0; i < 10; i++) {
subFuncs[i] = function() {
return i;
};
}
return subFuncs;
}
// 那麼,如何解決上訴的閉包坑呢?
// 其實原理很簡單,既然閉包坑的本質是:子函數對父函數變量的引用,是父函數運行結束之後的變量的狀態
// 那麼我們解決這個問題的方式就是:子函數對父函數變量的引用,使用運行時的狀態
// 如何做呢?
// 在函數表達式的基礎上,加上自執行即可。
function superFunc() {
var subFuncs = new Array();
for (var i = 0; i < 10; i++) {
subFuncs[i] = function(num) {
return function() {
return num;
};
}(i);
}
return subFuncs;
}
綜上,閉包本身不是什麼複雜的機制,就是子函數可以訪問父函數的作用域。
而由於JavaScript函數的特殊性,我們可以返回函數,如果我們將作爲閉包的函數返回,那麼該函數引用的父函數變量是父函數運行結束之後的狀態,而不是運行時的狀態,這便是閉包最大的坑。而爲了解決這個坑,我們常用的方式就是讓函數表達式自執行。
此外,由於閉包引用了祖先函數的作用域,所以濫用閉包會有內存問題。
好像把閉包說得一無是處,那麼閉包有什麼用處呢?
主要是封裝吧...
封裝
閉包可以封裝私有變量或者封裝塊級作用域。
➙ 封裝塊級作用域
JavaScript並沒有塊級作用域的概念,只有全局作用域和函數作用域,那麼如果想要創建塊級作用域的話,我們可以通過閉包來模擬。
創建並立即調用一個函數,就可以封裝一個塊級作用域。該函數可以立即執行其中的代碼,內部變量執行結束就會被立即銷燬。
function outputNumbers(count) {
// 在函數作用域下,利用閉包封裝塊級作用域
// 這樣的話,i在外部不可用,便有了類似塊級作用域
(function() {
for (var i = 0; i < count; i++) {
alert(i);
}
})();
alert(i); //導致一個錯誤!
}
// 在全局作用域下,利用閉包封裝塊級作用域
// 這樣的話,代碼塊不會對全局作用域造成污染
(function() {
var now = new Date();
if (now.getMonth() == 0 && now.getDate() == 1) {
alert("Happy new year!");
}
})();
// 是的,封裝塊級作用域的核心就是這個:函數表達式 + 自執行!
(function() {
//這裏是塊級作用域
})();
➙ 封裝私有變量
JavaScript也沒有私有變量的概念,我們也可以使用閉包來實現公有方法,通過隱藏變量暴露方法的方式來實現封裝私有變量。
(function() {
//私有變量和私有函數
var privateVariable = 10;
function privateFunction() {
return false;
}
//構造函數
MyObject = function() {};
//公有/特權方法
MyObject.prototype.publicMethod = function() {
privateVariable++;
return privateFunction();
};
})();