var functionName = function(arguments){
//函數體
}
我們通過例子來一步步說明,遞歸的最佳實現方式。下面是普通遞歸調用的例子:
// 階乘的遞歸函數
var factorial = function(num){
if(num <= 1){
return 1;
}else {
return num * factorial(num-1);
}
}
console.log(factorial(3)); //6
我們來看一下這種情況:
// 賦給一個變量
var anotherFactorial = factorial;
factorial = null;
// 調用遞歸函數
console.log(anotherFactorial(3));
運行結果:
TypeError: factorial is not a function
這裡提示錯誤,說factorial不是一個函數,因為我們已經把factorial設置為null,而在執行 anotherFactorial(3) 時,是通過factorial(num-1) 來遞歸調用的,所以就報錯,因為已經把factorial設置為null。
解決的策略就是使遞歸調用的函數內部不要出現外部定義的函數名。
我們可以通過命名表達式來實現(注意括號的使用):
var factorial = (function f(num){
if(num <= 1){
return num;
}else {
return num * f(num-1);
}
});
console.log(factorial(3)); //6
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(3)); //6
匿名函數:function後面沒有跟著函數名的函數
我們常見的匿名函數有函數表達式:
var functionName = function(arguments){
//函數體
}
當然匿名函數也可以不要賦值給變量:
(function(num){
console.log(num+1);
})(3);
運行結果:
4
匿名函數也可以作為函數返回:
function person(){
return function(){
console.log("TabWeng");
}
}
person()();
運行結果:
TabWeng
對於 person()(),有些人不太理解,其實不難理解,這裡解釋一下:
首先來看一下 person(),我們是不是得到一個返回的匿名函數 function(){console.log("TabWeng");},既然我們得到了一個函數,對於函數的調用,是不是給它的尾部加一個括號就可以了,所以就寫成person()()。
閉包:有權訪問另一個函數作用域的函數
匿名函數是function後面沒有跟著函數名的函數,和閉包的定義不同,盡管這兩個稱呼經常說的是同一個函數,但是根據功能的不同,應該加以區分。
如果你理解作用域鏈,那麼閉包就非常好理解。通過閉包的這種特性,我們可以來實現JavaScript的很多模式,更加靈活的運用JavaScript。
如果要訪問一個函數的作用域,我們可以在函數裡面創建一個閉包,對於閉包而言,被訪問的函數處在閉包作用域鏈的第二層(從前端到終端的順序),而作用域鏈的指針指向的是整個活動對象。
有一個典型的例子不得不講:
function printNum(){
var nums = [];
for(var i = 0; i < 3; i++){
nums[i] = function(){
return i;
}
}
for(var j = 0; j < nums.length; j++){
console.log(nums[j]());
}
}
printNum();
運行結果:
3
3
3
為什麼會得到這樣的結果,存在兩個疑問:
我們先來分析一下出現的原因:
首先針對問題2,在閉包裡面有return i;
,而在閉包中,i是沒有定義的,根據作用域鏈,會向上一層作用域鏈尋找i,在上一層中我們發現了i,我們是通過尋找上一層的活動對象來找到i的,既然是活動對象,裡面的數值就是最終的值,所有此時的i已經是最終的值3了,我獲得的i就是最終的值3。因此打印出來每個結果都是3。
知道了問題2出現的原因,那麼問題1也就是自然明了,在for循環中,i加到等於3,通過i<3
,使i沒有進入for循環的裡面,盡管 i==3 沒有進入for循環,但是閉包在獲取i的時候,獲取的就是i的最後一個值3。
通過立即執行函數來解決這個問題:
function printNum(){
var nums = [];
for(var i = 0; i < 3; i++){
nums[i] = function(num){
return num;
}(i);
}
for(var j = 0; j < nums.length; j++){
console.log(nums[j]);
}
}
printNum();
運行結果:
0
1
2
使用閉包時,因為閉包的作用域鏈會引用活動對象,使這個活動對象無法被回收(內存),而如果這個引用一直存在,那麼內存將一直無法得到釋放。通常的解決方法是解除這個引用。(解除引用的方法很多,主要是要有這個性能優化的思想,知道存在的問題)。
(function(){
//私有作用域
})();
看這塊代碼,之所以稱為私有,通過作用域鏈我們可以知道,外部不能訪問裡面。
而通過閉包可以實現提供公有方法而使外部能對私有變量進行訪問。
function Person(name){
var name = name;
var sayName = function(){
console.log(name);
};
this.publicSayName = function(){
console.log("我訪問了name這個屬性了:"+name);
sayName();
};
}
var p1 = new Person("TabWeng");
p1.publicSayName();
運行結果:
我訪問了name這個屬性了:TabWeng
TabWeng
這塊代碼也可以這樣寫:
function Person(name){
var name = name;
var sayName = function(){
console.log(name);
};
return function(){
console.log("我訪問了name這個屬性了:"+name);
sayName();
};
}
var p1 = new Person("TabWeng");
p1();
運行結果:
我訪問了name這個屬性了:TabWeng
TabWeng
通過返回的匿名函數獲得私有變量。
當然,也可以這樣寫:
function Person(name){
var name = name;
var sayName = function(){
console.log(name);
};
return {
printString:"我訪問了name這個屬性了:"+name,
sayName:sayName,
publicMethod:function(){
sayName();
}
};
}
var p1 = new Person("TabWeng");
console.log(p1.printString);
p1.sayName();
p1.publicMethod();
運行結果:
我訪問了name這個屬性了:TabWeng
TabWeng
TabWeng
這就是模塊模式,返回的是一個對象,盡管是對象字面量的形式,也可以得到私有變量。
- 《JavaScript高級程序設計》