JavaScript基礎---做用域,匿名函數和閉包

匿名函數就是沒有名字的函數,閉包是可訪問一個函數做用域裏變量的函數。設計模式

一.匿名函數數組

//普通函數瀏覽器

function box() { //函數名是 box閉包

return 'TT';函數

}性能

 

//匿名函數學習

function () { //匿名函數,會報錯this

return 'TT';spa

}prototype

 

//經過表達式自我執行

(function box() {       //封裝成表達式

alert('TT');

})();   //()表示執行函數,而且傳參

 

//把匿名函數賦值給變量

var box = function () { //將匿名函數賦給變量

return 'TT';

};

alert(box()); //調用方式和函數調用類似

 

//函數裏的匿名函數

function box () {

return function () { //函數裏的匿名函數,產生閉包

return 'TT';

}

}

alert(box()());   //調用匿名函數

 

二.閉包

閉包是指有權訪問另外一個函數做用域中的變量的函數, 建立閉包的常見的方式, 就是在一個函數內部建立另外一個函數,經過另外一個函數訪問這個函數的局部變量。

代碼示例:

 1 //經過閉包能夠返回局部變量
 2 
 3 function box() {
 4 
 5     var user = 'TT';
 6 
 7     return function () {     //經過匿名函數返回 box()局部變量
 8 
 9         return user;
10 
11     };
12 
13 }
14 
15 alert(box()());    //經過 box()()來直接調用匿名函數返回值
16 
17 var b = box();
18 
19 alert(b());     //另外一種調用匿名函數返回值

 

使用閉包有一個優勢, 也是它的缺點: 就是能夠把局部變量駐留在內存中, 能夠避免使用全局變量。(全局變量污染致使應用程序不可預測性,每一個模塊均可調用必將引來災難,因此推薦使用私有的,封裝的局部變量)。

 

//1. 經過全局變量來累加

 1 var age = 100;         //全局變量
 2 
 3 function box() {
 4 
 5     age ++;     //模塊級能夠調用全局變量,進行累加
 6 
 7 }
 8 
 9 box();       //執行函數,累加
10 
11 alert(age);   //輸出全局變量

 

 

//2. 經過局部變量沒法實現累加

 1 function box() {
 2 
 3     var age = 100;   //局部變量
 4 
 5     age ++;     //累加
 6 
 7     return age;
 8 
 9 }
10 
11 alert(box());   //101
12 
13 alert(box());   //101,沒法實現,由於又被初始化了

 

 

//3. 經過閉包能夠實現局部變量的累加

 1 function box() {
 2 
 3     var age = 100;
 4 
 5     return function () {
 6 
 7         age ++;
 8 
 9         return age;   //返回age,實現局部變量的駐留
10 
11     }
12 
13 }
14 
15 var b = box();    //得到函數
16 
17 alert(b());     //調用匿名函數
18 
19 alert(b());     //第二次調用匿名函數,實現累加
20 
21                  

 

PS:因爲閉包裏做用域返回的局部變量資源不會被馬上銷燬回收,因此可能會佔用更多的內存。過分使用閉包會致使性能降低,建議在很是有必要的時候才使用閉包。

 

問題:做用域鏈的機制致使一個問題,在循環中裏的匿名函數取得的任何變量都是最後一個值。

代碼以下:

 1 //循環裏包含匿名函數
 3 function box() {
 4 
 5     var arr = [];
 6 
 7     for (var i = 0; i < 5; i++) {
 8 
 9         arr[i] = function () {
10 
11             return i;
12 
13         };
14 
15     }
16 
17     return arr;
18 
19 }
20 
21 var b = box();     //獲得函數數組
22 
23 alert(b.length);   //獲得函數集合長度
24 
25 for (var i = 0; i < b.length; i++) {
26 
27     alert(b[i]());     //輸出每一個函數的值,都是最後一個值 5
28 
29 }

 

分析:上面的例子輸出的結果都是 5,也就是循環後獲得的最大的 i 值。由於 b[i]調用的是匿名函數,匿名函數並無自我執行,等到調用的時候,box()已執行完畢,i 早已變成 5,因此最終的結果就是 5 個 5。

 

//改 1: 自我執行匿名函數

 1 function box() {
 2 
 3     var arr = [];
 4 
 5     for (var i = 0; i < 5; i++) {
 6 
 7         arr[i] = (function (num) {       //自我執行
 8 
 9         return num;
10 
11         })(i);          //而且傳參
12 
13     }
14 
15     return arr;
16 
17 }
18 
19 var b = box();
20 
21 for (var i = 0; i < b.length; i++) {
22 
23     alert(b[i]);        //這裏返回的是數組,直接打印便可
24 
25 }
26 
27          

 

改 1 中,咱們讓匿名函數進行自我執行,致使最終返回給 a[i]的是數組而不是函數了。最終 b[0]-b[4]中保留了 0,1,2,3,4 的值。

 

//改 2: 匿名函數下再作個匿名函數

(原理同前面經過閉包實現局部變量的累加相似,閉包可使變量駐留在內存中)

//由於每次調用外層函數時的參數不一樣。每次被調用的時候,它(被返回的嵌套函數)建立的做用域也有些許不一樣。

//也就是說,對於外層函數的每次調用,都會在做用域鏈中產生一個不一樣的調用對象。(做用域鏈的知識見本系列2)

 1 function box() {
 2 
 3     var arr = [];
 4 
 5     for (var i = 0; i < 5; i++) {
 6 
 7         arr[i] = (function (num) {
 8 
 9             return function () {     //直接返回值,改 2 變成返回函數
10 
11                 return num;       //原理和改 1 同樣
12 
13             }
14 
15         })(i);
16 
17     }
18 
19 return arr;
20 
21 }
22 
23 var b = box();
24 
25 for (var i = 0; i < b.length; i++) {
26 
27     alert(b[i]());   //這裏經過 b[i]()函數調用便可
28 
29 }

 

改 1 和改 2 中,咱們經過匿名函數自我執行,當即把結果賦值給 a[i]。每個 i,是調用方經過按值傳遞的,因此最終返回的都是指定的遞增的 i。而不是box()函數裏的 i。

 

關於 this 對象

在閉包中使用 this 對象也可能會致使一些問題,this 對象是在運行時基於函數的執行環境綁定的,若是 this 在全局範圍就是 window,若是在對象內部就指向這個對象。而閉包卻在運行時指向 window 的,由於閉包並不屬於這個對象的屬性或方法。

代碼示例:

 1 var user = 'The Window';
 2 
 3 var obj = {
 4 
 5     user : 'The Object',
 6 
 7     getUserFunction : function () {
 8 
 9         return function () {   //閉包不屬於 obj, 裏面的 this 指向 window
10 
11             return this.user;
12 
13         };
14 
15     }
16 
17 };
18 
19 alert(obj.getUserFunction()());   //The window

 

 

//1. 能夠強制指向某個對象

alert(obj.getUserFunction().call(obj));      //The Object

//2. 也能夠從上一個做用域中獲得對象

getUserFunction : function () {

var that = this;      //從對象的方法裏得對象

return function () {

return that.user;

};

}

 

內存泄漏

因爲 IE 的 JScript 對象和 DOM 對象使用不一樣的垃圾收集方式, 所以閉包在 IE 中會致使一些問題。 就是內存泄漏的問題, 也就是沒法銷燬駐留在內存中的元素。

代碼示例:

 1 function box() {
 2 
 3     var oDiv = document.getElementById('oDiv');   //oDiv 用完以後一直駐留在內存
 4 
 5     oDiv.onclick = function () {
 6 
 7         alert(oDiv.innerHTML);   //這裏用 oDiv 致使內存泄漏
 8 
 9     };
10 
11 }
12 
13 box();
14 
15 
16 //那麼在最後應該將 oDiv 解除引用來避免內存泄漏。
17 
18 //修正:
19 
20 function box() {
21 
22     var oDiv = document.getElementById('oDiv');
23 
24     var text = oDiv.innerHTML;
25 
26     oDiv.onclick = function () {
27 
28         alert(text);
29 
30     };
31 
32     oDiv = null;   //解除引用
33 
34 }

 

PS:若是並無使用解除引用,那麼須要等到瀏覽器關閉才得以釋放。

 

模仿塊級做用域

JavaScript 沒有塊級做用域的概念。

代碼示例1:

function box(count) {

for (var i=0; i<count; i++) {}

alert(i);   // i 不會由於離開了 for 塊就失效

}

box(2);

 

代碼示例2:

function box(count) {

for (var i=0; i<count; i++) {}

var i; //就算從新聲明,也不會覆蓋前面的值(可是若是初始化,會執行這個值)

alert(i);

}

box(2);

 

以上兩個例子,說明 JavaScript 沒有塊級語句的做用域,if () {} for () {}等沒有做用域,若是有,出了這個範圍 i 就應該被銷燬了。就算從新聲明同一個變量也不會改變它的值。JavaScript 不會提醒你是否屢次聲明瞭同一個變量;遇到這種狀況,它只會對後續的聲明視而不見(若是初始化了,固然還會執行的)。使用模仿塊級做用域可避免這個問題。

//模仿塊級做用域(私有做用域)

//使用塊級做用域(私有做用域)改寫

 1 function box(count) {
 2 
 3     (function () {
 4 
 5         for (var i = 0; i<count; i++) {}
 6 
 7     })();
 8 
 9     alert(i);   //報錯,沒法訪問
10 
11 }
12 
13 box(2);

 

使用了塊級做用域(私有做用域)後,匿名函數中定義的任何變量,都會在執行結束時被銷燬。 這種技術常常在全局做用域中被用在函數外部, 從而限制向全局做用域中添加過多的變量和函數。 通常來講, 咱們都應該儘量少向全局做用域中添加變量和函數。 在大型項目中,多人開發的時候,過多的全局變量和函數很容易致使命名衝突,引發災難性的後果。 若是採用塊級做用域(私有做用域),每一個開發者既可使用本身的變量,又沒必要擔憂搞亂全局做用域。

(function () {

var box = [1,2,3,4];

alert(box);       //box 出來就不認識了

})();

在全局做用域中使用塊級做用域能夠減小閉包占用的內存問題, 由於沒有指向匿名函數的引用。只要函數執行完畢,就能夠當即銷燬其做用域鏈了。

 

私有變量

JavaScript 沒有私有屬性的概念;全部的對象屬性都是公有的。不過,卻有一個私有變量的概念。 任何在函數中定義的變量, 均可以認爲是私有變量, 由於不能在函數的外部訪問這些變量。

function box() {

var age = 100; //私有變量,外部沒法訪問

}

 

而經過函數內部建立一個閉包,那麼閉包經過本身的做用域鏈也能夠訪問這些變量。 而利用這一點,能夠建立用於訪問私有變量的公有方法。代碼以下:

 1 function Box() {
 2 
 3     var age = 100;           //私有變量
 4 
 5     function run() {          //私有函數
 6 
 7         return '運行中...';
 8 
 9     }
10 
11     this.get = function () {   //對外公共的特權方法
12 
13         return age + run();
14 
15     };
16 
17 }
18 
19 var box = new Box();
20 
21 alert(box.get());    

能夠經過構造方法傳參來訪問私有變量。代碼以下:

 1 function Person(value) {
 2 
 3     var user = value;     //這句能夠省略
 4 
 5     this.getUser = function () {
 6 
 7         return user;
 8 
 9     };
10 
11     this.setUser = function (value) {
12 
13         user = value;
14 
15     };
16 
17 }

 

可是對象的方法, 在屢次調用的時候, 會屢次建立。 可使用靜態私有變量來避免這個問題。

 

靜態私有變量(相似按引用傳遞)

經過塊級做用域(私有做用域)中定義私有變量或函數, 一樣能夠建立對外公共的特權方法。

代碼示例:

 1 (function () {
 2 
 3          var user = ''                                  //私有變量
 4 
 5          //function Box() {}             
 6 
 7          Box = function (value) {            //全局,構造函數
 8 
 9                    user = value;
10 
11          };
12 
13          Box.prototype.getUser = function () {
14 
15                    return user;
16 
17          };
18 
19          Box.prototype.setUser = function (value) {
20 
21                    user = value;
22 
23          }
24 
25 })();
26 
27  
28 
29 var box = new Box('TT');                   //第一次實例化
30 
31 alert(box.getUser());                          //TT
32 
33 var box2 = new Box('CC');                 //第二次實例化
34 
35 alert(box.getUser());                          //CC
36 
37 box2.setUser('OO');
38 
39 alert(box.getUser());  //OO,用box2設置,用box調用說明實現共享

 

上面的對象聲明, 採用的是 Box = function () {} 而不是 function Box() {} (就像匿名函數裏面的function run(){...}方法同樣,而Box由於沒有var關鍵字,因此是全局的!)由於若是用後面這種,就變成私有函數了,沒法在全局訪問到了,因此使用了前面這種。

使用了 prototype 致使方法共享了,而 user 也就變成靜態屬性了。(所謂靜態屬性,即共享於不一樣對象中的屬性)。

 

 模塊模式

以前採用的都是構造函數的方式來建立私有變量和特權方法。 那麼對象字面量方式就採用模塊模式來建立。

代碼示例:

(第一次實例化,沒法第二次實例化,那麼就是單例)

var box = { //字面量對象,也是單例對象

age : 100, //這是公有屬性,將要改爲私有

run : function () { //這時公有函數,將要改爲私有

return '運行中...';

};

};

 

代碼示例:

私有化變量和函數:

 1 var box = function () {
 2 
 3          var user = 'TT';                                      //私有變量
 4 
 5          function run() {                                      //私有函數
 6 
 7                    return '運行中...';  
 8 
 9          }
10 
11          return {
12 
13                    publicGo : function () {               //對外公共接口的特權方法
14 
15                             return user + run();
16 
17                    }
18 
19          };
20 
21 }();
22 
23  
24 
25 alert(box.publicGo());

上面的直接返回對象的例子,也能夠這麼寫:

 1 var box = function () {
 2 
 3          var user = 'TT';                                      //私有變量
 4 
 5          function run() {                                      //私有函數
 6 
 7                    return '運行中...';  
 8 
 9          }
10 
11          var obj =  {
12 
13                    publicGo : function () {         //對外公共接口的特權方法
14 
15                             return user + run();
16                    }
17          };
18 
19          return obj;
20 
21 }();
22 
23 alert(box.publicGo());

 

字面量的對象聲明, 其實在設計模式中能夠看做是一種單例模式, 所謂單例模式, 就是永遠保持對象的一個實例。

加強的模塊模式,這種模式適合返回自定義對象,也就是構造函數。

 1 function Desk() {}
 2 
 3 var box = function () {
 4 
 5          var user = 'TT';                                      //私有變量
 6 
 7          function run() {                                      //私有函數
 8 
 9                    return '運行中...';  
10 
11          }
12 
13          var desk = new Desk();   //實例化自定義對象
14 
15          desk.publicGo = function () {
16 
17                    return user + run();
18 
19          };
20 
21          return desk;
22 
23 }();
24 
25 
26 alert(box.publicGo());
27 
28  

 學習筆記,感謝李老師~

 發文不易,若轉載傳播,請親註明出處,謝謝!

相關文章
相關標籤/搜索