談談Javascript的匿名函數

JQuery 裏面有這麼一種代碼:javascript

(function(){ 
	// code here
})();

當一個匿名函數被括起來,而後再在後面加一個括號,這個匿名函數就能當即運行起來,神奇吧!java

要說匿名函數,咱們首先要由函數自己提及。函數的定義以下:函數是將惟一的輸出值賦予給每一輸入的「法則」。
web

固然,這只是數學上的定義。可是,在計算機編程語言中,函數的定義也八九不離十。由於咱們都知道,計算機中的函數,也相似數學定義中的描述,它是將輸入的若干數據,通過代碼設定的邏輯操做處理後,返回惟一的輸出的一組代碼組合塊。——固然,特例是,輸入的數據爲空或輸出的數據爲空,或者二者都爲空。編程

下面,咱們先初步瞭解一下和匿名函數相關的概念。瀏覽器

函數聲明(function 語句)

要使用一個函數,咱們就得首先聲明它的存在。而咱們最經常使用的方式就是使用function語句來定義一個函數,如:閉包

function abc(){   
	// code to process 
}

固然,函數也能夠是帶參數的,甚至是帶返回值的。編程語言

function abc(x,y){   
	return x+y; 
}

可是,不管你怎麼去定義你的函數,JS解釋器都會把它翻譯成一個Function對象。例如,你在定義上面的其中一個例子的函數號,再輸入以下代碼:ide

alert(typeof abc);	// "function"

你的瀏覽器就會彈出提示框,提示你abc是一個Function對象。那麼Function對象到底是什麼呢?函數式編程

Function 對象

Function對象是JavaScript裏面的固有對象,全部的函數實際上都是一個Function對象。咱們先看看,Function對象能不能直接運用構造函數建立一個新的函數呢?答案是確定的。例如:函數

var abc = new Function("x","y","return x*y;"); 
alert(abc(2,3)); // "6"

相信你們如今對如何聲明一個函數應該是有所瞭解了。那麼什麼纔是匿名函數呢?

什麼是匿名函數?

顧名思義,匿名函數就是沒有實際名字的函數。例如,咱們把上面的例子中,函數的名字去掉,再判斷一下他是否是一個函數:

alert(typeof function(){});// "function" 
alert(typeof function(x,y){return x+y;});// "function" 
alert(typeof new Function("x","y","return x*y;"))// "function"

咱們能夠很容易地看到,它們全都是Function對象,換言之,他們都是函數,可是他們都有一個特色——沒有名字。因此咱們把他們稱做「匿名函數」。然而,正由於他們沒有「名字」,咱們也沒有辦法找到他們。這就引伸瞭如何去調用一個匿名函數的問題了。

在Javascript定義一個函數通常有以下三種方式:

    • 函數關鍵字(function)語句:
function fnMethodName(x){
	alert(x);
}
    • 函數字面量(Function Literals):
var fnMethodName = function(x){
	alert(x);
}
    • Function()構造函數:
var fnMethodName = new Function('x','alert(x);')

匿名函數的調用

要調用一個函數,咱們必需要有方法定位它,引用它。因此,咱們會須要幫它找一個名字。例如:

var abc=function(x,y){   
	return x+y; 
} 
alert(abc(2,3)); // "5"

上面的操做其實就等於換個方式去定義函數,這種用法是咱們比較頻繁遇到的。例如咱們在設定一個DOM元素事件處理函數的時候,咱們一般都不會爲他們定名字,而是賦予它的對應事件引用一個匿名函數。

對匿名函數的調用其實還有一種作法,也就是咱們看到的jQuery片斷——使用()將匿名函數括起來,而後後面再加一對小括號(包含參數列表)。咱們再看一下如下例子:

alert((function(x,y){return x+y;})(2,3));// "5" 
alert((new Function("x","y","return x*y;"))(2,3));// "6"

不少人或許會奇怪,爲何這種方法能成功調用呢?以爲這個應用奇怪的人就看一下我如下這段解釋吧。

你們知道小括號的做用嗎?小括號能把咱們的表達式組合分塊,而且每一塊,也就是每一對小括號,都有一個返回值。這個返回值實際上也就是小括號中表達式的返回值。因此,當咱們用一對小括號把匿名函數括起來的時候,實際上小括號對返回的,就是一個匿名函數的Function對象。所以,小括號對加上匿名函數就如同有名字的函數般被咱們取得它的引用位置了。因此若是在這個引用變量後面再加上參數列表,就會實現普通函數的調用形式。

不知道以上的文字表述你們能不能看明白,若是仍是理解不了的話,再看一下如下的代碼試試吧。

// 把匿名函數對象賦給abc
var abc=function(x,y){return x+y;}; 
// abc的constructor就和匿名函數的constructor同樣了。也就是說,兩個函數的實現是同樣的。 
alert((abc).constructor==(function(x,y){return x+y;}).constructor);

PS:constructor是指建立對象的函數。也就是函數對象所表明的函數體。

總之,將其(被小括號包含的匿名函數)理解爲括號表達式返回的函數對象,而後就能夠對這個函數對象做正常的參數列表調用了。(前面這裏犯了個錯誤,只有函數表達式仍是不能直接調用函數的,去掉匿名函數括號必需要伴隨將表達式賦值。也就是(function(){alert(1)})()應該是與 a=function(){alert(1)}()等價,不能連a=都去掉。)

關於閉包

閉包是什麼?閉包是指某種程序語言中的代碼塊容許一級函數存在而且在一級函數中所定義的自由變量能不被釋放,直到一級函數被釋放前,一級函數外也能應用這些未釋放的自由變量。

怎樣?看得一頭冒汗吧……沒事,我也是(雖然是我是瞭解的,只是表達能力的問題)。讓咱們換個更加簡單的方法說明:閉包,實際上是一種語言特性,它是指的是程序設計語言中,容許將函數看做對象,而後能像在對象中的操做搬在函數中定義實例(局部)變量,而這些變量能在函數中保存到函數的實例對象銷燬爲止,其它代碼塊能經過某種方式獲取這些實例(局部)變量的值並進行應用擴展。

不知道這麼再解釋後會否更加清晰,若是仍是不明白,那麼咱們再簡化一下:閉包,其實就是指程序語言中能讓代碼調用已運行的函數中所定義的局部變量。

如今咱們看一個例子:

var abc=function(y){ 
	var x=y;	// 這個是局部變量 
	return function(){ 
		// 就是這裏調用了閉包特性中的一級函數局部變量的x,並對它進行操做     
		alert(x++);	
		alert(y--);	// 引用的參數變量也是自由變量 
	}
}(5);
// 初始化 
abc();// "5" "5" 
abc();// "6" "4" 
abc();// "7" "3" 
alert(x);
// 報錯!「x」未定義!

看到這裏,你能判斷究竟jQuery的那個代碼片斷是否閉包了嗎?

以個人理解來講吧。是否應用了閉包特性,必須肯定該段代碼有沒有最重要的要素:未銷燬的局部變量。那麼很顯然,沒有任何實現的匿名函數不可能應用了閉包特性。但若是匿名函數裏面有實現呢?那也還得肯定它的實現中有沒有用到那些未銷燬的局部變量。因此若是問你那個開篇中的jQuery代碼片斷是應用了JS裏的什麼特性?那麼它只是匿名函數與匿名函數的調用而已。可是,它隱含了閉包的特性,而且隨時能夠實現閉包應用。由於JS天生就是有這個特性的。

函數字面量和Function()構造函數的區別

雖然函數字面量是一個匿名函數,但語法容許爲其指定任意一個函數名,當寫遞歸函數時能夠調用它本身,使用Function()構造函數則不行。

var f = function fact(x) { 
	if (x < = 1) 
		return 1; 
	else 
		return x*fact(x-1); 
};

Function()構造函數容許運行時Javascript代碼動態的建立和編譯。在這個方式上它相似全局函數eval()。

Function()構造函數每次執行時都解析函數主體,並建立一個新的函數對象。因此當在一個循環或者頻繁執行的函數中調用Function()構造函數的效率是很是低的。相反,函數字面量卻不是每次遇到都從新編譯的。

用Function()構造函數建立一個函數時並不遵循典型的做用域,它一直把它看成是頂級函數來執行。

var y = "global"; 
function constructFunction() { 
	var y = "local"; 
	return new Function("return y"); 
	//  沒法獲取局部變量 
} 
alert(constructFunction()()); // 輸出 "global" 

和函數關鍵字定義相比Function()構造器有本身的特色且要難以使用的多,因此這項技術一般不多使用。而函數字面量表達式和函數關鍵字定義很是接近。考慮前面的區別,雖然有消息說字面量的匿名函數在OS X 10.4.3下的某些webkit的引擎下有bug,但咱們日常所說的匿名函數均指採用函數字面量形式的匿名函數。更多詳細內容能夠閱讀 《JavaScript: The Definitive Guide, 5th Edition》的Functions那章。

匿名函數的代碼模式

匿名函數這麼寫會報錯:

//其沒法工做,瀏覽器會報語法錯。   
function(){ 
	alert(1); 
}();  

可是前面加個=號就沒錯了:

var sum = function() {
	return 1;
}();

首先,用括號包圍起來的 function(){...} 被js 引擎認爲是一個function表達式 ,後面的 () 就調用了這個匿名函數。

若是你寫成 function(){...}() 那就是語法錯誤,由於js 引擎會期待這裏是一個有名字的function聲明 (func聲明與func表達式是不一樣的);

可是能夠寫成 void function(){...}() 也是能夠的,由於void關鍵字會告訴js 引擎後面要跟一個表達式,因此後面就被認出來是一個function表達式所產生的匿名函數對象,並進行調用的結果,void會把調用結果拋棄。

    1. 函數字面量: 首先聲明一個函數對象,而後執行它。
(function(){ 
	alert(1); 
}) ();
    1. 優先表達式: 因爲JavaScript執行表達式是從圓括號裏面到外面,因此能夠用圓括號強制執行聲明的函數。
( function(){ 
	alert(2); 
} () );
    1. void操做符: 用void操做符去執行一個沒有用圓括號包圍的一個單獨操做數。
void function(){ 
	alert(3); 
}() 

這三種方式是等同的,我的緣由比較喜歡第3種,而在實際應用中我看到的和使用的都是第1種。

匿名函數的應用

《Javascript的一種模塊模式》中的第一句話就是「全局變量是魔鬼」。配合var關鍵字,匿名函數能夠有效的保證在頁面上寫入Javascript,而不會形成全局變量的污染。這在給一個不是很熟悉的頁面增長Javascript時很是有效,也很優美。實際上,YUI以及其相應的範例中大量使用匿名函數,其餘的Javascript庫中也不乏大量使用。

Javascript的函數式編程(functional programming)的基石。具體請看《用函數式編程技術編寫優美的 JavaScript》和《函數式JavaScript編程指南》。

相關文章
相關標籤/搜索