1: function functionName(arg0,arg1){
2:
3:
4:
5: }
6:
7: var functionName = function( arg0 , arg1 ){
8:
9:
10:
11: }
匿名函數時一種強大的令人難以置信的工具
這兩種的區別, 前者會在代碼執行以前被加載到作用域中, 而后者則是在代碼執行到那一行的時候才會有定義.
另外的區別是, 函數聲明( 前者) 會給函數一個名字, 而函數表達式(后者) 則是創建一個匿名函數, 然后把這個匿名函數賦給一個變量 functionName.
關於函數聲明, 它的一個重要特征就是 函數聲明提升, 意思是在執行代碼之前會先讀取函數聲明, 這就意味着可以把函數聲明放在調用它的語句后面.
//匿名函數, 直接寫也是可以的,只不過沒有什么實際意義。
把函數想成值時, 都可以使用匿名函數 例如 :
在將函數作為參數傳入另一個函數,或者從一個函數中返回另一個函數時,通常都要使用以這種形式來定義匿名函數。
1: function createCompare(propertyName){
2:
3: return function( object1, object2 ){
4:
5: var value1 = object1[ propertyName] ;
6:
7: var value1 = object2[ propertyName] ;
8:
9: if ( value1 < value2 ){
10:
11: return 1 ;
12:
13: }else {
14:
15: return -1;
16:
17: }
18:
19:
20:
21: } ;
22:
23: }
遞歸 ( 用途 1 )
1: <SPAN style="COLOR: #000000">function factorial( num ){
2:
3: if(num <=1 ){
4:
5: return 1 ;
6:
7: } else {
8:
9: return num * factorial( num -1 ) ;
10:
11: }
12:
13: }
14:
15: var anotherFactorial = factorial ;
16:
17: factorial = null ;
18:
19: alert( anotherFactorial(4) ) ; // 出錯, 因為此時 factorial = null , 已經不再可用, 所以第一次調用沒問題, 因為使用 anotherFactorial 指針, 可以找到對應的函數, 但是函數內部,
20:
21: // 由於是遞歸函數, 所以函數內部的名字 factorial 失效 ( null ) , 所以就會出錯
22:
23: return arguments.callee( num - 1 ) ; // 可以解決問題, 之前有提到過, 因此遞歸函數時, 使用 arguments.callee 總比使用函數名好
24: </SPAN>
使用 arguments.callee( num –1 );
1: function factorial( num ){
2:
3: if (num <= 1){
4:
5: return 1;
6:
7: } else (
8:
9: return num * arguments.callee( num -1 ) // 其中 arguments.callee 是一個指向正在執行的函數的指針
10:
11: )
12:
13: }
14:
15: var anotherFactorial = factorial ;
16:
17: factorial = null ;
18:
19: alert( anotherFactorial(4) ) ;
閉包
閉包是指 有權訪問另一個函數作用域中的變量的函數. 創建閉包的常見方式, 是在一個函數內部創建另一個函數,建議非必要時, 不要使用閉包,匿名函數也是差不多。
即,閉包是一個函數,是能夠訪問別的函數作用域變量的函數,那么內部函數肯定能訪問外部函數的變量, 所以,此時就用到了匿名函數 ( 閉包 ), 所以閉包和匿名函數是什么關系的,是包含關系,即閉包肯定是個匿名函數。例如:
1: function createComparisonFunction(propertyName){
2:
3: return function( object1, object2){
4:
5: var value1 = object1[propertyName];
6:
7: var value2 = object2[propertyName];
8:
9: if (value1 < value2){
10:
11: return -1;
12:
13: } else if ( value1 > value2){
14:
15: return 1;
16:
17: } else {
18:
19: return 0;
20:
21: }
22:
23: };
24:
25: }
內部函數訪問了外部函數變量 prototyName,即使這個內部函數被返回了,而且是在其他地方被調用了,它仍然可以訪問變量 prototyName,內部函數的作用域鏈包括外部函數,
當某個函數第一次被調用時, 會創建一個執行環境及相應的作用域鏈, 並把作用域鏈賦值給一個特殊的內部屬性[Scope] , 然后, this, arguments 和其他命名參數的值來初始化函數的活動對象.但在作用域鏈中, 內部函數-> 外部函數-> 更外部函數->......-> 最外部函數. ( 這種包含關系 )
1: function compare(value1, value2){
2: if(value1 < value2){
3: return -1;
4: }else if(value1 > value2){
5: return 1;
6: }else{
7: return 0;
8: }
9:
10: }
11:
12: var result = compare(5,10);
對於這個例子中compare()函數的執行環境,其作用域鏈中包含兩個變量對象,本地活動對象和全局變量對象,顯然,作用域鏈本質上是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。
后台的每個執行環境都有一個表示變量的對象-變量對象, 全局環境的變量對象始終存在, 而像compare()函數這樣的局部環境變量對象, 則只在函數執行的過程中存在, 在創建compare()函數時, 會創建一個預先包含全局變量對象的作用域鏈, 這個作用域鏈保存在內部的[[Scope]]屬性中, 當調用 compare()函數時, 會為函數創建一個執行環境, 然后通過復制函數的[[Scope]]屬性中的對象構建起執行環境的作用於鏈. 此后, 又有一個活動對象(在此作為變量對象使用) 被創建並被推入執行環境作用域鏈的前端, 對於這個例子 compare() 函數的執行環境而言, 其作用域鏈中包含兩個變量對象, 本地活動對象和全局變量對象, 顯然作用域鏈本質上是一個指向變量對象的指針列表, 它只引用但不實際包含變量對象.
無論什么時候在函數中訪問一個變量時,就會從作用域鏈中搜索具有相應名字的變量,一般來講,當函數執行完畢后,局部活動對象就會被銷毀,內存僅保存全局作用域,但是閉包的情況不同.
在另一個函數內部定義的函數會將包含函數( 即 外部函數 )的活動對象添加到它的作用域鏈中。
承接以上代碼例子 :
var compare = createComparisonFunction(“name”); //compare指針保存函數地址
var result = compare( {name : “Nicholas”}, {name : “Greg”});
在匿名函數從 createComparisonFunction()中被返回后,它的作用域鏈被初始化為包含createComparisonFunction()函數的活動對象和全局對象,這樣,匿名函數就可以訪問createComparisonFunction()中定義的所有變量, 更為重要的是, createComparisonFunction()函數執行完畢后,其活動對象也不會被銷毀,因為匿名函數的作用域鏈仍然在引用這個活動對象。(那么匿名函數為什么沒有被釋放呢? 因為createComparisonFunction 這個函數本身返回的是一個函數, 所以compare=createComparisonFunction 這條語句的本質就是又做了一個指針指向了內存中的匿名函數, 所以在釋放空間時, 因為匿名函數在外圍函數的外圍還有一個指針指向, 所以它不能被釋放, 同樣它指向的外圍函數內部的變量也不能被釋放)換句話說,當createComparisonFunction()函數返回后,其執行環境的作用域鏈會被銷毀,但它的活動對象仍然會留在內存中,直到匿名函數被銷毀后,createComparisonFunction()的活動對象才會被銷毀。所以需要顯示解除 : 例如:
// 創建函數
var compareNames = createComparisonFunction("name"); // 這是 compareNames 已經是指向了函數內部的匿名函數
// 調用函數, // 注意, 這個函數執行完畢后, 函數的內存空間將被釋放, 但是... 匿名函數和匿名函數所指向的函數的內部變量不能被釋放
var result = comareNames({name: "Nicholas"}, {name: “Greg”});
// 解除對匿名函數的引用( 以便釋放內存 )
compareNames = null;
注意: ( anonymous ) Scope 中的 this 是 window, 這也就呼應了下邊的 this 的問題.
關於函數參數傳遞(特別是匿名函數 )

1 /* 匿名函數傳參辦法 */ 2 3 /* 01. 普通函數 */ 4 5 var aa = createFunction( xx ); // xx為實參 6 7 /* 02. 匿名函數 */ 8 var aa = function(num){}(argu); // num為形參, argu為實參
作用域鏈的副作用,即閉包只能取得包含函數中的任何變量的最后一個值,而不是某個特殊變量。
1: function createFunctions(){
2: var result = new Array();
3: for(var i=0; i<10; i++){
4: result[i] = function(){
5: return i;
6: };
7: }
8: return result;
9: }
10:
11: var funcs = createFunctions();
12: //每個函數都輸出10
13: for(var i=0; i<funcs.length; i++){
14: document.write(funcs[i]());
15: }
注意:
var funcs = createFunctions(); // 這個 createFunctions() 函數會執行, 並將返回結果 賦值給 funcs, 那么返回的是數組, funcs 就是數組, 返回的是函數 funcs 是函數.
var funcs = createFunctions; // 這個 createFunctions 函數不會執行, 只是又定義了一個指針指向該函數.
首先, 來說createFunction函數的返回結果, 它所返回的不是一個數字, 而是一個函數, 所以, 當我們接到返回結果時, 因為是一個函數, 而funcs[i]() 表示執行這個函數, 而此時執行這個函數, 這個函數只有1條語句 return i, 而這時因為已經退出了 createFunction函數, 所以i的值是9. 所以最后顯示的結果全是9, 之前有誤區 :
誤區1 createFunctions()函數返回的數值, 首先 return result 是返回語句, 表示此函數返回的是一個數組, 那么數組中裝的是什么的, 很直接的是函數, 而並非是 i 的值, 如果是i的值, 那么后面輸出時也就不用寫成funcs[i]()了, 而直接寫成 funcs[i]就可以了. 注意閉包內部名沒有執行, 因為開始是個賦值語句, result[i]=function(){}; 這個賦值成功了就可以了, 函數內部並沒有執行, 函數必須后面跟()后才能執行, 剩下的就是單純的賦值, 不要想的那么復雜.
誤區2 以為result[i]=function(){}; 這一步會執行function里的內容, 其實直到funcs[i]() 這個語句的時候, 閉包里的內容才真正的執行, 包括 var funcs = createFunctions(); 這條語句, 閉包都沒有執行, 這條語句時 createFunction 這個函數執行了, 並把結果 包含有函數的數組返回給了funcs, 這個時候 funcs就是一個數組了, 並且數組元素是一個function, 所以才可以調用 funcs[i](), 來執行數組元素函數.
上例中,每個函數都返回10,而不是1,2,3,4,5,6,7,8,9,10,因為每個函數的作用域鏈中都保存着createFunctions()函數的活動對象,所以它們引用的都是 同一個變量 i , 當createFunctions()函數返回后,變量 i 的值是 10 , 此時每個函數都引用這保存變量 i 的同一個變量對象,(如上, var funcs = createFunctions() 這條語句會執行 createFunctions函數, 執行后, 函數內的 i 的值就是10了, 並且將數組返回給了 funcs, 此時 funcs 就是一個數組, 而數組元素是 函數, 注意, 此處的數組元素並非數字, 所以當下邊又執行 document.write(funcs[i]()))時, 實際上是在執行數組元素里的函數, 也就是匿名函數, 而此時, 匿名函數會去調用createFunctions 中的 i , 而此時, i 已經是10了, createFunction這個函數還在作用域鏈的原因就是因為有匿名函數還在引用它. 所以整個 createFunction函數的作用域都沒有被釋放. )
所以在每個函數內部 i 的值都是 10。改進如下 :
1: function createFunctions(){
2: var result = new Array();
3: for(var i=0; i<10; i++){
4: result[i] = function(num){
5: return function(){
6: return num;
7: }(i);
8:
9: };
10: }
11: return result;
12: }
13:
14: var funcs = createFunctions();
15:
16: for(var i=0; i<funcs.length; i++){
17: document.write(funcs[i]());
18: }
這里, 我們沒有直接把閉包賦值給數組, 而是定義了一個匿名函數, 並立即執行該匿名函數, 將結果賦值給數組, 這里匿名函數的參數是 num, 每次執行時會將 i 傳遞進去, 由於函數參數是按值傳遞的, 所以就會將變量 i 的當前值賦值給參數 num, 而在這個匿名函數內部, 又創建了一個訪問 num 的閉包, 這樣一來, result 數組中每個函數都有自己的 num 變量的一個副本, 因此就有各自不同的值了. 注意這里的匿名函數, 如下:
result[i] = function(num) { // 匿名函數
return function() { // 閉包, 所有的閉包都是匿名函數
return num;
}
}(i); // (i) 表示要執行
這個匿名函數之所以能夠立刻執行, 就是因為后邊的(i), 函數名稱后邊帶(), 就表示要執行函數, 正式因為要執行函數, 所以 var funcs = createFunctions(); 要執行函數 createFunctions, 而進入createFunction 后, 又要執行函數 function(num), 注意上邊例子的閉包是不需要執行這個的, 這里就有一個閉包, result[i] 里邊存放的也都是函數, result[i] 里的函數不需要執行, 但是此時的 num 所處的環境是在 function(num) 這里, 這里每個 num 的值已經不同, 當再執行 document.write(funcs[i]()); 調用閉包的匿名函數時, 同樣此時在執行 result[i] 里的函數, 而這時的函數所返回的內容是 num, 而 num 因為每個所處的環境不同, 都在各自的 function(num) 環境中, 所以它們之間沒有影響, 所以最后返回的結果是, 1,2,3,4,5,6,7,8,9,10.
function createFunction() { var result = new Array(); for (var i=0; i<=9; i++) { result[i] = function() { return i; }(); } return result; } var funs = createFunction(); for(var i=0; i<funs.length; i++) { alert(funs[i]); }
所以, 以上是更簡單的得到0,1,2,3,4,5,6,7,8,9的方法, 只要在原來的基礎上, result[i] = function(){return i;}(); 加上這一對小括號, 讓這個閉包在函數內部執行就可以了
this對象
在閉包中使用 this 對象也可能會有點問題,this對象是在運行時基於函數的執行環境綁定的,在全局函數中,this等於window,在函數被作為某個對象的方法調用時,this等於那個對象。不過, 匿名函數的執行環境具有全局性, 因此 this 對象通常指向 windows.
1: var name = "The window";
2:
3: var object = {
4: name : "My Object",
5: getNameFunc : function(){
6: return function(){
7: return this.name;
8: }
9: }
10: };
11:
12: alert(object.getNameFunc()); //"The window"
以上代碼先創建了全局變量 name, 又創建了一個包含 name 屬性的對象, 這個對象還包含一個方法-getNameFunc(), 它返回一個匿名函數, 而匿名函數又返回 this.name,
為什么上邊例子執行結果是 The window ?
前面曾經提過, 每個函數在被調用時, 其活動對象都會自動取得兩個特殊變量 this 和 arguments, 內部函數在搜索這兩個變量時, 只會搜索到活動對象為止, 因此永遠不可能訪問到外部函數中的這兩個變量, 不過, 把外部作用域中的 this對象保存在一個閉包能夠訪問的到的變量里, 就可以讓閉包訪問到該變量了,
個人總結, 因為閉包會將它所在的函數的作用域鏈全部保存起來, 所有就有類似的事情, 閉包->外部函數->再外部函數->全局, 這是閉包的作用域鏈, 因為閉包全部保存了, 而又由於閉包只能取得以上作用域鏈的最后值, 例如上邊例子 i, 都是顯示10 一樣, 所以當你定義的變量名字 name 與環境變量同名時, 閉包要搜索到作用域鏈最后的這個 name, 所以就后這個例子就返回了 全局 name 屬性.
匿名函數的執行環境具有全局性,所以它的 this 屬性都是 window, 並且 this 是關鍵字。看下邊例子 :

1 var name = "The window"; 2 3 var object = { 4 name : "My object", 5 getNameFunc : function(){ 6 var that = this; // 此時是可以訪問this的, 因為this是 7 // 這個對象的一個屬性, 8 return function() { // 閉包 9 return that.name; // that 作為外層函數的活動對象, 10 // 會被引用(注意不是this, this不會 11 // 被引用, 12 }; 13 } 14 };
之所以這個會顯示我們想要的結果, 是因為在這個作用域鏈上沒有 that 同名的屬性, 所以當閉包搜索 that 屬性時, 這個 Object 里的 that 就是最頂端了, 換句話說是最后了, 所以自然就要顯示這個 that 的結果了.
模仿塊級作用域
JavaScript中沒有塊級作用域的概念, 例如 for 循環中的 i , 當離開 for 循環時, 也還繼續存在, 而在 C 和 Java 這些語言中就不一樣了,
function outputNumbers(count) {
for (var i=0; i<count; i++) {
alert(i);
}
alert(i); // 此處的 i, 仍然可以調用, 因為沒有塊級別作用域
}
一般來說, 我們應該盡量少向全局作用域中添加變量和函數. 通過這種創建私有作用域, 每個開發人員既可以使用自己的變量, 又不必擔心搞亂全局作用域.
1: ( //此處的 (
2:
3: function(){
4:
5: //這里是塊級作用域
6:
7: }
8:
9: ) ( ) ; // 此處的 ) ( ) ;
以上代碼定義並立即調用了一個匿名函數, 將函數聲明包含在一對圓括號中, 表示它實際上是一個函數表達式, 而緊隨其后的另一對圓括號會立即調用這個函數.
如果只是單純的寫 function(){
}();
這樣會出現錯誤,因為 Javascript 將 function 關鍵字當做一個函數聲明的開始,而函數聲明后面不能跟圓括號,然而,函數表達式后面可以跟圓括號(表示執行函數),要將函數聲明轉換成函數表達式,只要在function前后加一個圓括號就可以了, 即
( function {
}) ();
無論什么地方,只要臨時需要一些變量,就可以使用似有作用域,例如 :

1 function outputNumbers(count) { 2 (function() { 3 for (var i=0; i<count; i++) { 4 alert(i); 5 } 6 })(); 7 alert( i ); //導致一個錯誤 8 }
1: var someFunction = function () {
2:
3: // 這里是塊級別作用域
4:
5: } ;
6:
7: someFunction(); //調用函數
調用函數的方式是在函數名后邊加一對圓括號 (), 即 someFunction()
臨時使用一些變量時, 可以使用這種塊級作用域
1: (
2:
3: function(){
4:
5: for ( var i = 0 ; i < count ; i++ ) {
6:
7: alert( i ) ;
8:
9: }
10:
11: }
12:
13: ) () ;
14:
15: alert ( i ) ; // 導致一個錯誤
私有變量
JavaScript中沒有私有成員概念, 不過, 有一個私有變量的概念, 任何函數中定義的變量, 都可以認為是私有變量, 外部不能訪問函數內部的私有變量, 但是, 可以通過在函數內部創建一個閉包( 即內部函數) , 那么閉包通過自己的作用域鏈也可以訪問這些變量, 而利用這一點, 就可以創建用於訪問私有變量的公有方法.
我們把有權訪問私有變量和私有方法的公有方法稱為 特權方法.
第一種,構造函數中定義的特權方法:
1: function MyObject(){
2:
3: var privateVariable = 10 ; //私有變量
4:
5: function privateFunction() {
6:
7: return false ;
8:
9: }
10:
11: this.publicMethod = function() { // 特權方法
12:
13: privateVariable++;
14:
15: return PrivateFunction();
16:
17: } ;
18:
19: }
另一種方法:
1: ( function(){
2: //似有變量和私有函數
3: var privateVariable = 10;
4: function privateFunction(){
5: return false;
6: }
7:
8: //構造函數
9: MyObject = function(){};
10:
11: //公有特權方法
12: MyObject.prototype.publicMethod = function(){
13: privateVariable++;
14: return privateFunction();
15: };
16: }
17:
18: )();

1 ( function() { 2 var name = ""; //私有變量 3 Person = function(value) { 4 name = value; 5 }; 6 Person.prototype.getName = function() { 7 return name; 8 }; 9 Person.prototype.setName = function(value) { 10 name = value; 11 }; 12 13 14 } ) (); 15 16 var person = new Person("Nicholas"); 17 alert( person.getName() ); // "Nicholas" 18 person.setName("Greg"); 19 alert( person.getName() ); // "Greg"