頭疼的閉包


前端初學者在學習時都會遇上一個很頭疼的問題-----閉包

那么什么是閉包? 

官方的解釋是:閉包是一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。

廣義上的閉包就是指一個變量在它自身作用域的被使用了,就叫發生了閉包。粗魯地理解:閉包就是能夠讀取其它函數內部變量的函數。 在js中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單粗暴地理解成“定義在一個函數內部的函數”,即一個函數嵌套了另一個函數 

閉包是很多語言都具備的特性,在js中,閉包主要涉及到js的幾個其他的特性:

作用域鏈,垃圾(內存)回收機制,函數嵌套......

 

變量的作用域 

Js變量的作用域分兩種:全局變量和局部變量。 

函數內部可以直接讀取全局變量。  

1 var n = 'RORO彥';
2 function f1() {
3     console.log(n);
4 }
5 f1();

 

在函數外部自然無法讀取函數內的局部變量。 

1   function f1(){
2     var n= 'RORO彥';
3 }
4 console.log(n); // error

   

此外,函數內部聲明變量時,一定要用var關鍵字來命名變量。否則,就聲明了一個全局變量。

當我們需要從外部讀取局部變量,得到函數內的局部變量,可以在函數的內部再定義一個函數。  

1   function f1(){
2     n = 'RORO彥';
3     function f2(){
4       console.log(n);
5     }
6 }

如上,函數f2就被包括在函數f1內部,這時f1內部的所有局部變量,對f2是可見的。反之,不行。這就是Javascript語言特有的鏈式作用域結構(chain scope,子對象會一級一級地向上尋找所有父對象的變量。

 

現在f2可以讀取f1中的局部變量,那么只要把f2作為返回值,我們就可以在f1外部讀取它的內部變量

1 function f1(){
2     n = 'RORO彥';
3     function f2(){
4         console.log(n);
5     }
6     return f2;
7 }
8 var result=f1();
9 result(); // RORO彥

 

 作用

1.讀取函數內部的變量

2.令這些變量的值始終保持在垃圾(內存)回收機制中。 

 1  function f1(){
 2     var n = 'RORO彥';
 3     add=function(){n = 'RORO';};
 4     function f2(){
 5         console.log(n);
 6     }
 7     return f2;
 8 }
 9 var result=f1();
10 result(); // RORO彥
11 add();
12 result(); // RORO

在這段代碼中,result實際上就是閉包f2函數。它一共運行了兩次,第一次是RORO彥,第二次是RORO。函數f1中的局部變量n一直保存在內存中,並沒有在f1調用后被自動清除,而是保存在內存堆(heap)里, 原因是它被包裝或封裝在一個函數體內,這些構造器都被稱為閉包。它返回調用函數的運行結果,是函數本身。此外,add的值是一個匿名函數,它本身也是一個閉包,所以add相當於是一個setter,可以在函數外部對函數內部的局部變量進行操作。

 

注意事項 

函數中的變量都保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。閉包會在父函數外部改變父函數內部變量的值。所以,當你把父函數當作對象使用,把閉包當作它的公用方法,把內部變量當作它的私有屬性,不要隨便改變父函數內部變量的值。

 

閉包與抽象數據類型

我們通過閉包能簡單地引入抽象數據類型。

例如,通過閉包實現一個 堆棧

function createStack() {

  var elements = [];


  return {

    push: function(el) { elements.unshift(el); },

    pop: function() { return elements.shift(); }

  };

}


var stack = createStack();


stack.push(3);

stack.push(4);

stack.pop(); // 4

閉包與面向對象編程

在 JavaScript 中,閉包不是堆棧數據類型的最佳實現方式。用原型 Prototype 實現對內存更友好,在當前對象實例找不到相應屬性或方法時,會到相應實例共同引用的 Prototype 屬性尋找相應屬性或方法。如果在當前Prototype屬性找不到時,會沿着當前原型鏈向上查找,而Prototype 上的屬性或方法是公用的,而不像實例的屬性或方法那樣,各自單獨創建屬性或方法,從而節省更多的內存。

上述構造器看起來非常像類、對象、實例值和私有/公有方法。閉包與類相似,都會將一些能操作內部數據的函數聯系在一起。於是,我們可以像使用對象一樣使用閉包。當我們想在 JavaScript 創建“真正的”隱藏域,或者需要創建簡單的構造器時,我們可以優先使用閉包。不過對於一般的類來說,閉包可能還是有點太繁重了。

 

注:參考資料《javascript權威指南》第6版中文版


免責聲明!

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



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