JS函數表達式——函數遞歸、閉包

一:定義函數的方式:

一、函數聲明;二、函數表達式java

函數聲明的重要特徵:函數聲明提高,在執行代碼以前會先讀取函數聲明。c++

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

函數表達式必須先賦值。數組

二:遞歸

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

下面代碼可能致使出錯bash

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);
	}
});
複製代碼

3、閉包

閉包是指有權訪問另外一個函數做用域中的變量的函數。當某個函數被調用時,會建立一個執行環境及相應的做用域鏈。而後,使用arguments和其餘命名參數的值來初始化函數的活動對象。ui

通常來講,當函數執行完畢後,局部活動對象就會被銷燬,內存中僅保存全局做用域(全局執行環境的變量對象)。可是,閉包的狀況有所不一樣。this

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

create()函數在執行完畢後,其活動對象不會被銷燬,由於匿名函數的做用域鏈仍然在引用這個活動對象。當create()函數返回後,其執行環境的做用域鏈會被銷燬,但它的活動對象仍然會留在內存中;直到匿名函數被銷燬後,create()活動對象纔會被銷燬。spa

一、閉包和變量指針

做用域鏈的這種配置,使閉包只能取得包含函數中任何變量的最後一個值。閉包保存的是整個變量對象,而不是某個特殊的變量。

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變量的一個副本,所以就能夠返回各自不一樣的數值了。

二、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()()就會當即調用它返回的函數,結果就是返回一個字符串。

三、內存泄漏

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,包含函數的活動對象中也仍然會保存一個引用。

4、模仿塊級做用域

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); //致使一個錯誤
}
複製代碼

5、私有變量

相關文章
相關標籤/搜索