JavaScript中的閉包與匿名函數

知識內容:html

1.預備知識 - 函數表達式數組

2.匿名函數閉包

3.閉包函數

 

 

 

1、函數表達式性能

1.定義函數的兩種方式this

函數聲明:spa

1 function func(arg0, arg1, arg2){
2     // 函數體  
3 }

函數表達式:prototype

1 var func = function (arg0, arg1, arg2){
2     // 函數體  
3 }

 

 

2.注意事項翻譯

函數表達式使用前必須賦值!像下面的代碼是錯誤的:設計

1 say()
2 var say = function(){
3     console.log("123")
4 }

函數表達式能夠做爲一個普通變量在分支中根據條件來賦值:

 1 // 下面的代碼將根據不一樣的condition 將不一樣的函數表達式賦給sayHi
 2 var sayHi
 3 var condition = true  // Hi!
 4 // var condition = false  // Hello!
 5 
 6 if(condition){
 7     sayHi = function(){
 8         console.log("Hi!")
 9     }
10 }
11 else{
12     sayHi = function(){
13          console.log("Hello!")
14     }
15 }
16 
17 sayHi()

上述代碼不一樣的運行效果:

能建立函數賦值給變量,固然也能夠把函數做爲其餘函數的返回值返回

 

 

 

2、匿名函數

1.什麼是匿名函數

匿名函數的基本形式爲(function(){...})();
前面的括號包含函數體,後面的括號就是給匿名函數傳遞參數並當即執行之

通常用到匿名函數的時候都是當即執行的。一般叫作自執行匿名函數或者自調用匿名函數。經常使用來構建沙箱模式,做用是開闢封閉的變量做用域環境,避免全局變量的污染,在多人聯合工做中,合併js代碼後,不會出現相同變量互相沖突的問題

 

 

2.匿名函數的詳細寫法

匿名函數的詳細寫法有如下兩種,推薦使用第一種:

1 (function(){ 
2   console.log("我是匿名方式1");
3 })();  // 我是匿名方式1
4 
5 (function(){ 
6   console.log("我是匿名方式2");
7 }());  // 我是匿名方式2
8 
9 console.log((function(){}).name);  // name爲空

注:實際上,當即執行的匿名函數並非函數,由於已經執行過了,因此它是一個結果,這個結果是對當前這個匿名函數執行結果的一個引用(函數執行默認return undefined)。這個結果能夠是一個字符串、數字或者null/false/true,也能夠是對象、數組或者一個函數(對象和數組均可以包含函數),當返回的結果包含函數時,這個當即執行的匿名函數所返回的結果就是典型的閉包了

 

 

3.匿名函數的做用

  • 函數表達式能夠存儲在變量中,而且能夠賦值,能夠做爲其餘函數的參數
  • 能夠經過匿名函數執行某些一次性的任務
  • 避免全局變量的污染以及函數名的衝突

 

 

 

3、閉包

1.什麼是閉包

什麼是閉包:閉包指有權訪問另外一個函數做用域中的變量的函數,官方對閉包的解釋是:一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分

  • 做爲一個函數變量的一個引用,當函數返回時,其處於激活狀態
  • 一個閉包就是當一個函數返回時,一個沒有釋放資源的棧區

簡單說Javascript容許使用內部函數---即函數定義和函數表達式位於另外一個函數的函數體內。並且這些內部函數能夠訪問它們所在的外部函數中聲明的全部局部變量、參數和聲明的其餘內部函數。當其中一個這樣的內部函數在包含它們的外部函數以外被調用時,就會造成閉包

 

 

2.閉包的寫法

1
2
3
4
5
6
7
8
9
10
function  a(){
   var  n = 0;
   function  inc() {
     n++;
     console.log(n);
   }
   inc();
   inc();
}
a(); //控制檯輸出1,再輸出2

簡單吧。再來看一段代碼:

1
2
3
4
5
6
7
8
9
10
function  a(){
   var  n = 0;
   this .inc = function  () {
     n++;
     console.log(n);
   };
}
var  c = new  a();
c.inc();  //控制檯輸出1
c.inc();  //控制檯輸出2

簡單吧。

什麼是閉包?這就是閉包!

有權訪問另外一個函數做用域內變量的函數都是閉包。這裏 inc 函數訪問了構造函數 a 裏面的變量 n,因此造成了一個閉包。

再來看一段代碼:

1
2
3
4
5
6
7
8
9
10
11
function  a(){
   var  n = 0;
   function  inc(){
     n++;
     console.log(n);
   }
   return  inc;
}
var  c = a();
c();  //控制檯輸出1
c();  //控制檯輸出2

看看是怎麼執行的:

var c = couter(),這一句 couter()返回的是函數 inc,那這句等同於 var c = inc;

c(),這一句等同於 inc();  注意,函數名只是一個標識(指向函數的指針),而()纔是執行函數。

後面三句翻譯過來就是:  var c = inc;  inc();  inc();,跟第一段代碼有區別嗎? 沒有

 

這樣寫的緣由:
咱們知道,js的每一個函數都是一個個小黑屋,它能夠獲取外界信息,可是外界卻沒法直接看到裏面的內容。將變量 n 放進小黑屋裏,除了 inc 函數以外,沒有其餘辦法能接觸到變量 n,並且在函數 a 外定義同名的變量 n 也是互不影響的,這就是所謂的加強「封裝性」。

而之因此要用 return 返回函數標識 inc,是由於在 a 函數外部沒法直接調用 inc 函數,因此 return inc 與外部聯繫起來,代碼 2 中的 this 也是將 inc 與外部聯繫起來而已

 

 

3.閉包的做用

  • 實現匿名自執行函數
  • 實現封裝
  • 實現類和繼承
  • 將變量的值一直保存在內存中

(1)閉包實現匿名自執行函數見上面匿名函數中的詳細介紹

 

(2)閉包實現封裝

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

私有變量包括:

  • 函數的參數
  • 函數的局部變量
  • 在函數內部定義的其餘函數

另外利用閉包原理,咱們能夠建立用於訪問私有變量的公有方法:在函數內部建立一個閉包,閉包經過做用域鏈訪問這些變量,從而實現外部沒法直接訪問內部變量只能經過某些方法來訪問,這樣就實現了封裝

 1 var person = function(){    
 2     // 變量做用域爲函數內部,外部沒法訪問    
 3     var name = "default";       
 4        
 5     return {    
 6        getName : function(){    
 7            return name;    
 8        },    
 9        setName : function(newName){    
10            name = newName;    
11        }    
12     }    
13 }();    
14      
15 print(person.name);  // 直接訪問,結果爲undefined    
16 print(person.getName());    
17 person.setName("wyb");    
18 print(person.getName());    
19    

 

(3)閉包實現類和繼承

 1 function Person(){    
 2     var name = "default";       
 3        
 4     return {    
 5        getName : function(){    
 6            return name;    
 7        },    
 8        setName : function(newName){    
 9            name = newName;    
10        }    
11     }    
12     };   
13 
14     var p = new Person();
15     p.setName("Tom");
16     alert(p.getName());
17 
18     var s= function(){};
19     // 繼承自Person
20     s.prototype = new Person();
21     // 添加私有方法
22     s.prototype.Say = function(){
23         alert("Hello,my name is s");
24     };
25     var j = new s();
26     j.setName("s");
27     j.Say();
28     alert(j.getName());

 

(4)將變量的值一直保存在內存中

 1 // 閉包將變量的值一直保存在內存中
 2 var f1 = function () {
 3     var n = 1;
 4     test = function () {
 5         n += 1;
 6     };
 7     var f2 = function () {
 8         alert(n);
 9     };
10     return f2;
11 };
12 
13 var res = f1();
14 alert(res());  // 1 undefined
15 test();
16 alert(res());  // 2 undefined

 

閉包的應用:

 1 // 上述閉包應用 - 迭代器
 2 var f = function (x) {
 3     var i = 0;
 4     return function () {
 5         return x[i++];
 6     }
 7 };
 8 var next = f([11, 22, 33, 44]);
 9 alert(next());  // 11
10 alert(next());  // 22
11 alert(next());  // 33
12 alert(next());  // 44

 

 

4.閉包注意事項

(1)閉包與變量

閉包只能取得包含函數中任何變量的最後一個值,閉包所保存的是整個變量對象,而不是某個特殊的變量

 1 function createFunctions(){
 2     var result = new Array()
 3 
 4     for (var i=0; i < 10; i++){
 5         result[i] = function(){
 6             return i;
 7         }
 8     }
 9 
10     return result
11 }    
12 
13 var foo = createFunction()

結果foo中並非預料中的那樣,而是10,這是爲何呢?僅僅聲明某一個函數,引擎並不會對函數內部的任何變量進行查找或賦值操做。只會對函數內部的語法錯誤進行檢查(若是往內部函數加上非法語句,那麼不用調用也會報錯),也就是說在返回result以前

result並未執行,返回以後再執行時匿名函數中的全部i引用的都是同一個變量(最後一個值10),故每一個函數內部i的值爲10

詳細說明看這一篇文章:http://www.javashuo.com/article/p-wuqideyr-z.html

經過建立另外一個匿名函數強制讓閉包的行爲符合預期:

 1 function createFunctions(){
 2     var result = new Array()
 3 
 4     for (var i=0; i < 10; i++){
 5         result[i] = function(num){
 6             return function(){
 7                  return num
 8             }
 9         }(i)
10     }
11 
12     return result
13 }  

 

(2)閉包中的this對象

關於this對象:

  • this對象是在運行時基於函數的執行環境綁定的
  • 在全局函數中this等價於window,當函數被做爲某個對象的方法調用時this等價於那個對象
  • 在閉包中使用this對象可能會致使一些問題
 1 var name = "The Window";
 2 var object = {
 3     name: "My object",
 4     getNameFunc: function() {
 5         return function() {
 6             return this.name;
 7         };
 8     }
 9 }
10 alert(object.getNameFunc()()); // "The Window"

爲何最後的結果是"The Window"而不是object裏面的name"My object"呢?

首先,要理解函數做爲函數調用和函數做爲方法調用,咱們把最後的一句拆成兩個步驟執行:

var first = object.getNameFunc();
var second = first();

其中第一步,得到的first爲返回的匿名函數,此時的getNameFunc()做爲object的方法調用,若是在getNameFunc()中使用this,此時的this指向的是object對象。

第二步,調用first函數,能夠很清楚的發現,此時調用first函數,first函數沒有在對象中調用,所以是做爲函數調用的,是在全局做用域下,所以first函數中的this指向的是window

再看下面這句話:

爲何匿名函數沒有取得其包含做用域(外部做用域)的this對象呢?

每一個函數被調用時,其活動對象都會自動取得兩個特殊變量:this和arguments。內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問外部函數中的這兩個變量。  《Javascript高級程序設計》

那麼,如何得到外部做用域中的this呢?能夠把外部做用域中的this保存在閉包能夠訪問到的變量裏。以下:

 1 var name = "The Window";
 2 var object = {
 3     name: "My object",
 4     getNameFunc: function() {
 5         var that = this;   // 將getNameFunc()的this保存在that變量中
 6         var age = 15;
 7         return function() {
 8             return that.name;
 9         };
10     }
11 }
12 alert(object.getNameFunc()());   // "My object"

 

(3)模仿塊級做用域

在JavaScript中沒有塊級做用域,JavaScript歷來不會告訴你是否屢次同時聲明同一個變量,遇到這種狀況它只會對後續的聲明視而不見,不過咱們可使用匿名函數和閉包來實現塊級做用域

用做塊級做用域的匿名函數以下:

1 (function(){
2    // 這裏是塊級做用域   
3 }) ();

上述匿名函數也等價於這種形式:

1 var someFunction = function(){
2     // 這裏是塊級做用域  
3 }
4 someFunction()

 

關於匿名函數及模仿塊級做用域的注意事項:

  • 在匿名函數中定義的任何變量都會在執行結束時被銷燬
  • 在私有做用域(匿名函數)中能夠訪問外層函數的變量是由於這個匿名函數是一個閉包,它可以訪問包含做用域中的全部變量

關於以上兩點,示例代碼以下:

1 function func(count){
2     (function (){
3         for(var i=0; i < count; i++){
4             console.log(i)      
5         }
6     })();  
7     
8     console.log(i)  // 致使一個錯誤!   
9 }

 

(4)閉包保存的是函數的做用域而不是變量自己的值

 1 // 閉包保存的是函數的做用域而不是變量自己的值
 2 var f = function (param) {
 3     var n = function () {
 4         return param;
 5     };
 6     param++;
 7     return n;
 8 };
 9 var test = f(456);
10 console.log(test());  // 457

注:經過閉包能夠實現將變量一直保存在內存中

 

(5)閉包會使得函數中的變量都保存在內存中,內存消耗很大,不能濫用閉包,不然會形成網頁的性能問題,在IE中可能會致使內存泄露,儘可能在退出函數以前,將不使用的局部變量所有刪除

 1 // 閉包將變量的值一直保存在內存中
 2 var f1 = function () {
 3     var n = 1;
 4     test = function () {
 5         n += 1;
 6     };
 7     var f2 = function () {
 8         alert(n);
 9     };
10     return f2;
11 };
12 
13 var res = f1();
14 alert(res());  // 1 undefined
15 test();
16 alert(res());  // 2 undefined
相關文章
相關標籤/搜索