1. 程式人生 > >簡單詳細講解js閉包(看完不懂你砍我!!!)

簡單詳細講解js閉包(看完不懂你砍我!!!)

  《javascript高階程式設計》中閉包的概念:

    閉包,其實是一種語言特性,它是指的是程式設計語言中,允許將函式看作物件,然後能像在物件中的操作般在函式中定義例項(區域性)變數,而這些變數能在函式中儲存到函式的例項物件銷燬為止,其它程式碼塊能通過某種方式獲取這些例項(區域性)變數的值並進行應用擴充套件。

    我們的理解:

      其實閉包就是一個函式,一個外部函式通過呼叫函式並return返回出內部函式,這裡的內部函式就是一個閉包;此時在內部函式中是可以訪問到外部函式的變數的;

    

    要想理解閉包,首先我們要了解棧堆記憶體和作用域鏈;首先我們來講解棧堆記憶體:

       首先我們來看個demo:

        

var a=1;
var obj={"name":"鹹魚"}

   上面簡單的兩句程式碼,其實就是在記憶體中做了兩件事,效果圖如下:

  

  在js簡單實現深淺拷貝(https://www.cnblogs.com/dengyao-blogs/p/11466598.html)一文中我們知道基本資料型別是儲存在棧記憶體中的,引用資料型別是儲存在堆記憶體中的,其實上面的兩句程式碼在記憶體中就是做了兩件事:1.首先在棧記憶體中開闢了一塊空間用來存放a的變數和值;2.在堆記憶體中開闢了一塊空間用來儲存obj的值,同時在將地址指向棧記憶體中的變數名obj

  如果我們在程式碼下面再加上一句obj={"name":'張三"},這個時候我們之前儲存name為鹹魚的值也就是obj原來的值會被js中的垃圾回收機制回收掉,然後obj的值重新的指向{name:"張三"}這個值;

 

  作用域鏈

   再來看一下這個例子:

var a = 1;
function fn(){
    var b = 2;
    function fn1(){
        console.log(b);//2
        console.log(a);//1
    }
    fn1();
}
fn(); 

 效果圖如下:

    

 

 

   1.var a=1;這個時候我們是在全域性執行環境的,瀏覽器的全域性環境就是window作用域,我們的window作用域中有a和fn;

  2.當我們往下走到fn的時候,棧記憶體會開闢一塊新的執行環境,此時fn的執行環境中我們有b和fn1;

  3.當我們接著往下走到fn1的時候,這時棧記憶體同樣會開闢一塊新的執行環境,此時fn1的執行環境中是沒有任何變數資料的,但是我們在fn1中輸出a、b,我們都是可以讀取到的;這是因為程式在讀取變數的時候是從內到外的開始讀的,是隨著fn1開始往上一層一層的查詢,是這樣的執行順序(fn1 = > fn = > window),如果找到window中還沒有讀取到變數,這時程式才會報錯;

  當然在執行的過程中,垃圾回收機制如果檢測到程式執行完了是會進行垃圾回收的,避免造成記憶體洩露等問題;就是說我們的fn1裡面執行完之後fn1的作用域就會被銷燬,接著程式執行fn,fn執行完之後fn就會被銷燬;往上執行到全域性的時候,整個程式就沒有了fn的作用域和fn1的作用域,只剩下瀏覽器的全域性作用域window,這個時候window裡只剩a和fn;

 

   瞭解了上面的作用域鏈和棧記憶體和堆記憶體的知識之後,我們來開始講解js閉包:

  

function outer() {
     var  a = '123'
    
    return function add(){
    //在這裡因為作用域的關係,add是能訪問到outer的所有變數的,但是outer是訪問不到add的變數;
    //所以思路一轉,把add的值作為結果return出來變通實現outer外部函式訪問到了內部函式變數
// add就是一個閉包函式,因為他能夠訪問到outer函式的作用域,add中沒有找到變數a,則會繼續往上層作用域找 console.log(a); } } var inner = outer() // 獲得add閉包函式 inner() //"123"

 

    首先我們可以看到,在全域性作用域下我們是有一個outer函式的,outer作用域裡面有a和add,add作用域裡面執行控制檯輸出a的變數,此時這裡的add函式就形成了一個閉包,因為add函式裡面需要訪問到outer作用域下的a變數,而他們不處在同一個作用域中,所以兩者相互牽引,需要輸出a,上面outer中的變數a就必須得在,輸出a的時候,垃圾回收機制會認為上面的程式還沒有執行完成,所以不會清理a的記憶體空間;所以這就會帶來一個問題:如果我們多次的使用閉包,則會給我們的程式帶來記憶體佔用過多,導致效能問題;

    函式內部能訪問全域性變數是javascript語言的特殊之處,但是如果我們想達到函式外部能訪問內部變數的時候,我們就可以使用閉包,這就是閉包給我們帶來的便利;

    閉包的優缺點:

      優點:

        1.可以讀取函式內部的變數
        2.可以避免全域性汙染

      缺點:

        1.閉包會導致變數不會被垃圾回收機制所清除,會大量消耗記憶體;

        2.不恰當的使用閉包可能會造成記憶體洩漏的問題;

總結:

  1.作用域鏈查詢變數的方式是一層一層的往上查詢,直到找到為止,如果找到window全域性作用域還未找到,就報undefined;

  2.巢狀函式中,因為不在同一作用域,正常情況下內部函式是訪問不到外部函式的,但是通過閉包可以實現;

  3.儘可能少的使用閉包,因為會造成記憶體消耗大以及有可能造成記憶體洩露(如果不需要的時候,不要隨便使用);