JavaScript--匿名函數和閉包(16)


// 匿名函數:沒有名字的函數;

// 閉包:可訪問一個函數作用域里的變量的函數;

一 匿名函數

 1 // 普通函數
 2     function box(){                       // 函數名是box;
 3         return 'Lee';                     
 4     }
 5     box();                                // =>Lee; 調用函數;
 6 // 匿名函數
 7     function(){                           // 匿名函數,會報錯;
 8         return 'Lee';
 9     }
10 // 通過表達式自我執行
11     (function(name){
12         console.log(name);               // =>Lee;
13     })("Lee");                           // "()"表示執行函數,並且可以傳參;
14 // 把匿名函數賦值給變量
15     var box = function(){                // 將匿名函數賦給變量;
16         return 'Lee';
17     };
18     console.log(box());                  // 調用方式和函數調用相似;
19 // 函數里的匿名函數
20     function box(){
21         return function(name){            // 函數里的匿名函數,產生閉包;
22             return name;
23         };
24     };
25     console.log(box()("Lee"));            // 函數box()調用匿名函數,並傳參;

二 閉包

// 閉包:有權訪問另一個函數作用域中的變量的函數;

// 創建閉包的常見方式:在一個函數內部創建另一個函數;通過另一個函數訪問這個函數的局部變量;

 1 // 通過閉包可以返回局部變量
 2     function box(){
 3         var user = 'Lee';
 4         return function(){                // 通過匿名函數返回box()的局部變量user;
 5             return user;
 6         };
 7     }
 8     console.log(box()());                // =>Lee; 直接調用box()()來獲得匿名函數的返回值;
 9 
10     var b = box();
11     console.log(b());                    // =>Lee; 另一種調用匿名函數方式;
12 
13 // 優點:可以把局部變量駐留在內存中,可以避免使用全局變量; 
14 // (全局變量污染導致應用程序不可預測性,每個模塊都可調用必將引來災難;所以推薦使用私有的,封裝的局部變量);

 

 1 // 缺點:
 2 // 通過全局變量來累加
 3     var age = 100;                        // 全局變量;
 4     function box(){
 5         age++;                            // 模塊級可以調用全局變量,進行累加;
 6     };
 7     box();                                // 執行函數,累加一次;
 8     console.log(age);                     // =>101; 輸出全局變量;
 9     box();                                // 執行函數,累加一次;
10     console.log(age);                     // =>102; 輸出全局變量;
11 // 通過局部變量無法實現累加
12     function box(){
13         var age = 100;
14         age++;                            // 實現累加;
15         return age;
16     }
17     console.log(box());                   // =>101;
18     console.log(box());                   // =>101; 無法實現累加,因為第二次調用函數時,函數內部變量age又被初始化了;
19 
20 // 通過閉包可以實現局部變量的累加
21     function box(){
22         var age = 100;
23         return function(){                // 匿名函數內實現累加;
24             age++;
25             return age;                   // 並返回累加后的變量; 
26         };                                // 此時box()函數的局部變量age的值已經被修改為累加后的值;
27     }
28     var b = box();                        // 給box()函數賦值給變量;
29     console.log(b());                     // =>101; 調用匿名函數,累加一次;
30     console.log(b());                     // =>102; 第二次調用匿名函數,累加兩次;
31 
32 // PS:由於閉包里作用域返回的局部變量資源不會被立刻銷毀回收,所以可能會占用更多的內存;所以過度使用閉包會導致性能下降;(將閉包引用在"私有作用域"中即可實現變量銷毀)
33 // 作用域鏈的機制導致一個問題,在循環中里的匿名函數取得的任何變量都是最后一個值; ?

 

 1 // 循環里包含匿名函數
 2     function box(){
 3         var arr = [];
 4         for(var i=0; i<5; i++){         // 當聲明變量i=5時,循環停止;而此時循環里的變量i==5; 
 5             arr[i] = function(){        // arr[i]得到的只是沒有執行的匿名函數function(){};
 6                 return i;                
 7             };
 8         };
 9         return arr;                     // arr = [function,function,function,function,function];
10     }
11     var b = box();                      // =>[function,function,function,function,function]; 得到函數box()返回的數組arr;
12     console.log(b.length);              // =>5; 得到函數集合數組長度;
13     for(var i=0; i<b.length; i++){
14         console.log(box()[i]());        // =>5,5,5,5,5; 輸出每個函數的值,都是最后一個值;
15     }
16     // 上面的例子輸出的結果都是5,也就是循環后得到的最大i值;
17     // 因為b[i]調用的是匿名函數,匿名函數並沒有自我執行,等到調用的時候,box()已執行完畢,i早已變成5;
18 
19 // 循環里包含匿名函數-改1,自我執行匿名函數
20     function box(){
21         var arr = [];
22         for(var i=0; i<5; i++){
23             arr[i] = (function(num){    // arr[i]得到的是匿名函數執行后的結果數值0-4;
24                 return num; 
25             })(i);                      // 自我執行並傳參;
26         }
27         return arr; 
28     }
29     var b = box();                      // =>[0,1,2,3,4];  此時b代表box()返回的數組;
30     for (var i = 0; i < b.length; i++) {
31         console.log(b[i]);              // 0 1 2 3 4; 這里返回的是數值;
32     };
33     // 例子中,我們讓匿名函數進行自我執行,導致最終返回給a[i]的是數組而不是函數了;最終導致b[0]-b[4]中保留了0,1,2,3,4的值;
34 
35 // 循環里包含匿名函數-改2,匿名函數里再做個匿名函數;
36     function box(){
37         var arr = []; 
38         for(var i=0; i<5; i++){
39             arr[i] = (function(num){
40                 return function(){      // 返回函數;
41                     return num;            
42                 }
43             })(i);
44         }
45         return arr;                     // arr = [function,function,function,function,function];
46     }
47     var b = box();
48     for (var i = 0; i < b.length; i++) {
49         console.log(b[i]());            // 0,1,2,3,4; 
50     };
51 
52 // 改1和改2中,我們通過匿名函數自我執行,立即把結果賦值給arr[i];
53 // 每一個i,是調用方通過按值傳遞的,所以最終返回的都是指定的遞增的i;而不是box()函數中的變量i;

三 this對象

 1 // 在閉包中使用this對象可能會導致一些問題;this對象是在運行時基於函數的執行環境綁定的;
 2 // 如果this在全局范圍就是指向window,如果在對象內部就指向這個對象;
 3 // 而閉包卻在運行時指向window的,因為閉包並不屬於這個對象的屬性或方法; 
 4     var user = 'Window';
 5     var obj = {
 6         user:'Object',
 7         getUserFunction:function(){
 8             return function(){                        // 閉包不屬於obj,里面的this指向window;
 9                 return this.user;
10             };
11         }
12     };
13     console.log(obj.getUserFunction()());            // =>Window;
14 
15     // 可以強制指向某個對象
16     console.log(obj.getUserFunction().call(obj));    // =>Object;
17 
18     // 也可以從上一個作用域中的得到對象
19     getUserFunction:function(){
20         var that = this;                             // 從對象的方法里得到this;此時that指向obj對象;
21         return function(){
22             return that.user;
23         }
24     }
25     console.log(obj.getUserFunction()());            // =>Object;

四 內存泄漏

 1 // 由於IE的JScript對象和DOM對象使用不同的垃圾收集方式,因此閉包在IE中會導致內存泄漏問題,也就是無法銷毀駐留在內存中的元素;
 2     function box(){
 3         var oDiv = document.getElementById('oDiv');  // oDiv用完之后一直駐留在內存中;
 4         oDiv.onclick = function(){
 5             alert(oDiv.innerHTML);                   // 這里用oDiv導致內存泄漏;
 6         };
 7         oDiv = null;                                 // 解除引用;
 8     }
 9     box();
10     // 由於匿名函數保存了一個對box()的活動對象的引用,因此就會導致無法減少oDiv的引用數;
11     // 只要匿名函數存在,oDiv的引用數至少也是1;因此它所占用的內存就永遠不會被回收;
12     // PS:如果沒有使用解除引用,那么要等到瀏覽器關閉才得以釋放;

五 模仿塊級作用域(定義並立即調用一個匿名函數)

 1 // JS沒有塊級作用域的概念;
 2 // 這意味着在塊語句(for語句/if語句)中定義的變量,實際上是在包含函數中而非語句中創建的;
 3     function box(count){
 4         for(var i=0; i<count; i++){}                // box(2); => count=2; i=2時循環停止,此時i=2;
 5         console.log(i);                             // =>2; i不會因為離開了for塊就失效;
 6     }
 7     box(2);
 8 
 9     function box(count){
10         for(var i=0; i<count; i++){}
11         var i;                                      // 就算重新聲明,也不會覆蓋前面的值;
12         console.log(i);
13     }
14     box(2);
15 // 在JavaScript中,變量i是定義在box()的活動對象中的,因此從它有定義開始,就可以在函數內部隨處訪問它;
16 // 以上兩個例子,說明JavaScript沒有塊級語句的作用域,if(){}/for(){}等沒有作用域;
17 // 如果有作用域的話,出了這個范圍i就應該被銷毀;
18 
19 // JavaScript不會提醒是否多次聲明了同一個變量;遇到這種情況,它只會對后續的聲明視而不見(如果是初始化並賦值,還是會執行的);
20 
21 // 模仿塊級作用域(私有作用域)
22     (function(){
23         // 這里是塊級作用域;
24     })();
25     // 以上代碼定義並立即調用了一個匿名函數;將函數聲明包含在一對圓括號中,表示它實際上是一個函數表達式;
26 
27 // 使用塊級作用域(私有作用域)改寫
28     function box(count){
29         (function(){
30             for(var i=0; i<count; i++){}
31         })();
32         console.log(i);                                // 報錯,無法訪問;變量i在私有作用域中,出了私有作用域即被銷毀了.
33     }
34     box(2);
35 // 使用了塊級作用域后,匿名函數中定義的任何變量,都會在執行結束時被銷毀;(i只能在循環中使用,使用后即被銷毀);
36 //私有作用域中能夠訪問變量count,是因為這個匿名函數是一個閉包,他能夠訪問包含作用域中的所有變量;
37 // 這種技術經常在全局作用域中被用在函數外部,從而限制向全局作用域中添加過多的變量和函數;
38 // 一般來說,我們都應該盡可能少向全局作用域中添加變量和函數;過多的全局變量和函數很容易導致命名沖突;
39 // 使用塊級作用域,每個開發者既可以使用自己的變量,又不必擔心搞亂全局作用域;
40     (function(){
41         var box = [1,2,3,4];
42         console.log(box);                            // =>[1,2,3,4]; box出來就不被認識了;
43     })();                                            // 銷毀匿名函數中的變量;
44     console.log(box);                                // =>box is not defined;
45     // 在全局作用域中使用塊級作用域可以減少閉包占用的內存問題;因為沒有指向匿名函數的引用
46     // 只要函數執行完畢,就可以立即銷毀其作用域鏈了;

六 私有變量

 1 // JavaScript沒用私有屬性的概念;所有的屬性都是公用的;
 2 // 不過有一個私有變量的概念:在任何函數中定義的變量,都可以認為是私有變量,因為不能在函數外部訪問這些變量;
 3 // 私有變量包括函數的參數/局部變量和在函數內部定義的其他函數;
 4     function box(){
 5         var age = 100;                                 // 私有變量,外部無法訪問;
 6     }
 7 
 8 //通過內部創建一個閉包,那么閉包通過自己的作用域鏈也可以訪問這些變量;
 9 // 而利用這一點,可以創建用於訪問私有變量的公用方法;特權方法;
10     function Box(){                                    // 構造函數;
11         var age = 100;                                 // 私有變量;
12         function run(){                                // 私有函數;
13             return '運行中...';
14         };
15         this.get = function(){                         // 對外公共的特權方法;
16             return age+run();                          // 將閉包賦值給變量;
17         };
18     }
19     var box = new Box();
20     console.log(box.get());
21 
22 // 可以通過構造方法傳參來訪問私有變量
23     function Person(name){
24         var user = name;                            // 這句其實可以省略;
25         this.getUser = function(){
26             return user;
27         };
28         this.setUser = function(name){
29             user = name;
30         }
31     }
32     var p = new Person('Lee');
33     console.log(p.getUser());                        // =>Lee;
34     console.log(p.setUser('Jack'));
35     console.log(p.getUser());                        // =>Jack;
36     // 但是,構造函數模式的缺點是針對每個實例都會創建同樣一組新方法;而使用靜態私有變量來實現特權方法就可以避免這個問題;

七 靜態私有變量

 1 // 通過塊級作用域(私有作用域)中定義私有變量或函數,同樣可以創建對外公共的特權方法;
 2     (function(){                                    // 創建私有作用域;
 3         var age = 100;                              // 靜態私有變量;
 4         function run(){
 5             return '運行中...';
 6         };
 7         Box = function(){};                         // 使用函數表達式定義構造函數;
 8         Box.prototype.go = function(){              // 公有(特權)方法;在原型上定義的;
 9             return age+run();
10         };
11     })();
12     var box = new Box();
13     console.log(box.go());                          // 100運行中...;
14 // 上面的對象聲明,采用的是Box = function(){}而不是functiong Box(){};並且在聲明Box時沒有使用var關鍵字
15 // 導致:初始化未經聲明的變量,總是會創建一個全局變量;因此,Box就成了一個全局變量,能夠在私有作用域之外被訪問到;
16 // 因為如果用函數聲明定義構造函數,那么就變成私有函數了,無法在全局訪問到了,所以要使用函數式定義構造方法;
17     (function(){
18         var user = "";
19         Person = function(value){                   // 此處定義的Person是全局變量;
20             user = value;                           // 這里的構造函數有權訪問私有變量name;
21         };
22         Person.prototype.getUser = function(){
23             return user;
24         };
25         Person.prototype.setUser = function(value){
26             user = value;
27         }
28     })();
29     var person = new Person();
30     person.setUser('Lee');
31     console.log(person.getUser());                    // =>Lee;
32 // 使用了prototype導致方法共享了,而user也就變成靜態屬性了;
33 // 所謂靜態屬性:即共享於不同對象中的屬性;?

八 模塊模式

 1 // 簡言之,如果必須創建一個對象並以某些數據對其進行初始化,同時還要公開一些能夠訪問這些私有數據的方法,那么就可以使用模塊模式;
 2 // 之前采用的都是構造函數的方式來創建私有變量和特權方法;
 3 // 那么對象字面量方式就采用模塊模式來創建;
 4     var box = {                                      // 字面量對象,也是單例對象:只有一個實例的對象;
 5         age:100,                                     // 這是公有屬性,將要改成私有;
 6         run:function(){
 7             return '運行中...';
 8         };
 9     };
10 
11 // 模塊模式私有化變量和函數:
12     var box = function(){
13         var age = 100;
14         function run(){
15             return '運行中...';
16         }
17         return {                                     // 將一個字面量對象作為函數的值返回;
18             go:function(){                           // 返回的對象字面量中只包含可以公開的屬性和方法;
19                 return age+run();                    // 由於這個對象是在匿名函數內部定義的,因此它的公有方法有權訪問私有變量和函數;
20             }                                    
21         };                                           // 從本質上講,這個對象字面量定義的是單例的公共接口;
22     }();
23 // 這種模式在需要對單例進行某些初始化,同時又需要維護其私有變量時是非常有用的; 
24 
25 // 上面直接返回對象的例子,也可以這么寫:
26     var box = function(){
27         var age = 100;
28         function run(){
29             return '運行中...';
30         }
31         var obj = {                                   // 創建字面量對象;
32             go:function(){
33                 return age+run();
34             }
35         };
36         return obj;                                   // 返回剛創建的對象;
37     }();
38 
39 // 字面量的對象聲明,其實在設計模式中可以看作是一種單例模式;
40 // 所謂單例模式,就是永遠保持對象的只有一個實例;
41 
42 // 增強的模塊模式:適合返回自定義對象,也就是構造函數;
43     function Desk(){};
44     var box = function(){
45         var age = 100;
46         function run(){
47             return '運行中...';
48         };
49         var desk = new Desk();
50         desk.go = function(){
51             return age+run();
52         };
53         return desk;
54     }();
55     console.log(box.go());                            // =>100運行中;

九 小結

// 在JavaScript編程中,函數表達式是一種非常有用的技術;使用函數表達式可以無須對函數命名,從而實現動態編程;

1.函數表達式

1 // 函數表達式不同於函數聲明;函數聲明要求有名字,但函數表達式不需要;
2 // 沒有名字的函數表達式叫做匿名函數;

2.閉包

1 // 當在函數內部定義了其他函數時,就創建了閉包.閉包有權訪問包含函數內部的所有變量;原理如下:
2 // 在后台執行環境中,閉包的作用域鏈包含着它自己的作用域、包含函數的作用域和全局作用域;
3 // 通常,函數的作用域及其所有變量都會在函數執行結束后被銷毀;
4 // 但是,當函數返回了一個閉包時,這個函數的作用域將會一直在內存中保存到閉包不存在為止;

3.塊級作用域

1 // 使用閉包可以在JavaScript中模仿塊級作用域(JavaScript本身沒有塊級作用域的概念);要點如下:
2 // 創建並立即調用一個函數,這樣既可以執行其中的代碼,又不會在內存中留下對該函數的引用;
3 // 結果就是函數內部的所有變量都會被立即銷毀--除非將某些變量賦值給了包含作用域(即外部作用域)中的變量;

4.私有變量

1 // 閉包還可以用於在對象中創建私有變量,要點如下:
2 // 即使JavaScript中沒有真是的私有對象屬性的概念,但是可以使用閉包來實現公有方法,而通過公有方法可以訪問包含作用域中定義的變量;
3 // 可以使用構造函數模式、原型模式來實現自定義類型的特權方法,也可以使用模塊模式來實現單例的特權方法;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM