JS函式表示式——函式遞迴、閉包

Jsp發表於2018-05-19

一:定義函式的方式:

1、函式宣告;2、函式表示式

函式宣告的重要特徵:函式宣告提升,在執行程式碼之前會先讀取函式宣告。

sayHi();
function sayHi(){
	console.log("Hi");
}
複製程式碼

函式表示式必須先賦值。

二:遞迴

function fact(num){
	if(num <= 1){
		return 1;
	}else{
		return num*fact(num-1);
	}
}
複製程式碼

下面程式碼可能導致出錯

var another = fact;
fact = null;
console.log(another(4)); //出錯
複製程式碼

使用arguments.callee可以解決這個問題,arguments.callee是一個指向正在執行的函式的指標。

function fact(num){
	if(num<=1){
		return 1;
	}else{
		return num*arguments.callee(num-1);
	}
}
複製程式碼

在嚴格模式下,不能通過arguments.callee訪問,建立一個名為f()的函式,將它賦值給fact。

var fact = (function f(num){
	if(num <= 1){
		return 1;
	}else{
		return num * f(num-1);
	}
});
複製程式碼

三、閉包

閉包是指有權訪問另一個函式作用域中的變數的函式。當某個函式被呼叫時,會建立一個執行環境及相應的作用域鏈。然後,使用arguments和其他命名引數的值來初始化函式的活動物件。

一般來說,當函式執行完畢後,區域性活動物件就會被銷燬,記憶體中僅儲存全域性作用域(全域性執行環境的變數物件)。但是,閉包的情況有所不同。

function create(name){
	return function(){
		return name;
	}
}
複製程式碼

create()函式在執行完畢後,其活動物件不會被銷燬,因為匿名函式的作用域鏈仍然在引用這個活動物件。當create()函式返回後,其執行環境的作用域鏈會被銷燬,但它的活動物件仍然會留在記憶體中;直到匿名函式被銷燬後,create()活動物件才會被銷燬。

1、閉包和變數

作用域鏈的這種配置,使閉包只能取得包含函式中任何變數的最後一個值。閉包儲存的是整個變數物件,而不是某個特殊的變數。

function create(){
	var result = new Array();
	for(var i =0;i<10;i++){
		result[i] = function(){
			return i;
		};
	}
	return result;
}
複製程式碼

陣列10個10,每個函式的作用域都儲存著create()函式的活動物件,所以引用的都是同一個變數i。

建立一個匿名函式強制讓閉包的行為符合預期:

function create(){
	var result = new Array();
	for(var i=0;i<10;i++){
		result[i] = function(num){
			return function(){
				return num;
			};
		}(i);
	}
	return result;
}
複製程式碼

這個函式中,沒有直接把閉包賦值給陣列,而是定義了一個匿名函式,並將立即執行該匿名函式的結果賦給陣列。這裡的匿名函式有一個引數num,也就是最終函式要返回的值。在呼叫每個匿名函式時,我們傳入了變數i。由於函式引數是按值傳遞的,所以就會將變數i的當前值複製給引數num。而在這個匿名函式內部,又建立並返回一個訪問num的閉包。這樣一來,result陣列中的每個函式都有num變數的一個副本,因此就可以返回各自不同的數值了。

2、this物件

this物件是在執行時基於函式的執行環境繫結的:在全域性函式中,this等於window,函式被當作某個物件的方法呼叫時,this等於那個物件。匿名函式的執行環境具有全域性性,因此this通常指向window。

建構函式當作普通函式呼叫時,this代表的是全域性window物件。和new使用建立物件,指向當前的物件。

var name = 'this window';
var object = {
	name: 'my object',
	getName: function(){
		return function(){
			return this.name;
		};
	}
};
alert(object.getName()()); //"this window"
複製程式碼

由於getName()返回一個函式,因此呼叫object.getName()()就會立即呼叫它返回的函式,結果就是返回一個字串。

3、記憶體洩漏

function assign(){
	var element = document.getElementById("someElement");
	element.onclick = function(){
		alert(element.id);
	};
}
複製程式碼

以上程式碼建立一個作為element元素事件處理程式的閉包,而這個閉包又建立了一個迴圈引用。由於匿名函式儲存了一個對assign()活動物件的引用,因此會導致無法減少element的引用數。只要匿名函式存在,element的引用數至少也是1,因此它佔用的記憶體就永遠不會被回收。

可通過以下解決:

function assign(){
	var element = document.getElementById("someElement");
	var id = element.id;
	element.onclick = function(){
		alert(id);
	};
	element = null;
}
複製程式碼

閉包會引用包含函式的整個活動物件,而其中包含著element。即使不直接引用element,包含函式的活動物件中也仍然會儲存一個引用。

四、模仿塊級作用域

JS沒有塊級作用域的概念。在塊語句中定義的變數,實際是在包含函式中而非語句中建立的。

function output(count){
	for(var i=0;i<count;i++){
		alert(i);
	}
	var i; //重新宣告變數,視而不見
	alert(i);
}
複製程式碼

在java、c++語言中,變數i只會在for迴圈的語句中有定義,迴圈一旦結束,變數i就會被銷燬。在JS中,變數i定義在output()的活動物件中,從它又定義開始就可以在函式內部訪問它。

用作塊級作用域的(私有作用域)的匿名函式的語法:

(function(){
	//這裡是塊級作用域
})();
複製程式碼

重寫output()函式:

function output(count){
	(function(){
		for(var i=0;i<count;i++){
			alert(i);
		}
	})();
	alert(i); //導致一個錯誤
}
複製程式碼

五、私有變數


相關文章