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

小白沖沖沖沖 發佈 2019-12-31T02:42:31+00:00

首先我們可以看到,在全局作用域下我們是有一個outer函數的,outer作用域裡面有a和add,add作用域裡面執行控制台輸出a的變量,此時這裡的add函數就形成了一個閉包,因為add函數裡面需要訪問到outer作用域下的a變量,而他們不處在同一個作用域中,所以兩者相互牽引,需要

《javascript高級程序設計》中閉包的概念:

閉包,其實是一種語言特性,它是指的是程序設計語言中,允許將函數看作對象,然後能像在對象中的操作般在函數中定義實例(局部)變量,而這些變量能在函數中保存到函數的實例對象銷毀為止,其它代碼塊能通過某種方式獲取這些實例(局部)變量的值並進行應用擴展。

我們的理解:

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

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

首先我們來看個demo:

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

上面簡單的兩句代碼,其實就是在內存中做了兩件事,效果圖如下:

image.png


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


我自己是一名從事了多年開發的web前端老程式設計師,目前辭職在做自己的web前端私人定製課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習乾貨,各種框架都有整理,送給每一位前端小夥伴,想要獲取的可以關注我的頭條號並在後台私信我:前端,即可免費獲取。

如果我們在代碼下面再加上一句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(); 

效果圖如下:


image.png

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就必須得在,作用域鏈查找到outer的時候找到a了,輸出a的時候,垃圾回收機制會認為add還沒有執行完成,因為此時的作用域鏈查找已經到了outer作用域下,所以不會清理a的內存空間;所以這就會帶來一個問題:如果我們多次的使用閉包,則會給我們的程序帶來內存占用過多,導致性能問題;

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

閉包的優缺點:

優點:
1.可以讀取函數內部的變量;

2.可以避免全局污染

缺點:
1.閉包會導致變量不會被垃圾回收機制所清除,會大量消耗內存;
2.不恰當的使用閉包可能會造成內存泄漏的問題;

總結:

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

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

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



作者:有夢想的鹹魚前端
連結:https://www.jianshu.com/p/a9fa4b58c777

關鍵字: