閉包是js的一個難點也是它的一個特點,是咱們必須掌握的js高級特性,那麼什麼是閉包呢?它又有什麼用呢?編程
咱們都知道,js的做用域分兩種,全局和局部,基於咱們所熟悉的做用域鏈相關知識,咱們知道在js做用域環境中訪問變量的權利是由內向外的,內部做用域能夠得到當前做用域下的變量而且能夠得到當前包含當前做用域的外層做用域下的變量,反之則不能,也就是說在外層做用域下沒法獲取內層做用域下的變量,一樣在不一樣的函數做用域中也是不能相互訪問彼此變量的,那麼咱們想在一個函數內部也有限權訪問另外一個函數內部的變量該怎麼辦呢?閉包就是用來解決這一需求的,閉包的本質就是在一個函數內部建立另外一個函數。跨域
咱們首先知道閉包有3個特性:緩存
①函數嵌套函數安全
②函數內部能夠引用函數外部的參數和變量閉包
③參數和變量不會被垃圾回收機制回收函數
本文咱們以閉包兩種的主要形式來學習性能
①函數做爲返回值學習
在這段代碼中,a()中的返回值是一個匿名函數,這個函數在a()做用域內部,因此它能夠獲取a()做用域下變量name的值,將這個值做爲返回值賦給全局做用域下的變量b,實現了在全局變量下獲取到局部變量中的變量的值.net
再來看一個閉包的經典例子線程
通常狀況下,在函數fn執行完後,就應該連同它裏面的變量一同被銷燬,可是在這個例子中,匿名函數做爲fn的返回值被賦值給了fn1,這時候至關於fn1=function(){var n = 0 ... },而且匿名函數內部引用着fn裏的變量num,因此變量num沒法被銷燬,而變量n是每次被調用時新建立的,因此每次fn1執行完後它就把屬於本身的變量連同本身一塊兒銷燬,因而乎最後就剩下孤零零的num,因而這裏就產生了內存消耗的問題
再來看一個經典例子-定時器與閉包
寫一個for循環,讓它按順序打印出當前循環次數
按照預期它應該依次輸出1 2 3 4 5,而結果它輸出了五次5,這是爲何呢?原來因爲js是單線程的,因此在執行for循環的時候定時器setTimeout被安排到任務隊列中排隊等待執行,而在等待過程當中for循環就已經在執行,等到setTimeout能夠執行的時候,for循環已經結束,i的值也已經編程5,因此打印出來五個5,那麼咱們爲了實現預期結果應該怎麼改這段代碼呢?(ps:若是把for循環裏面的var變成let,也能實現預期結果)
引入閉包來保存變量i,將setTimeout放入當即執行函數中,將for循環中的循環值i做爲參數傳遞,100毫秒後同時打印出1 2 3 4 5
那若是咱們想實現每隔100毫秒分別依次輸出數字,又該怎麼改呢?
在這段代碼中,至關於同時啓動3個定時器,i*100是爲4個定時器分別設置了不一樣的時間,同時啓動,可是執行時間不一樣,每一個定時器間隔都是100毫秒,實現了每隔100毫秒就執行一次打印的效果。
②閉包做爲參數傳遞
在這段代碼中,函數fn1做爲參數傳入當即執行函數中,在執行到fn2(30)的時候,30做爲參數傳入fn1中,這時候if(x>num)中的num取的並非當即執行函數中的num,而是取建立函數的做用域中的num這裏函數建立的做用域是全局做用域下,因此num取的是全局做用域中的值15,即30>15,打印30
最後總結一下閉包的好處與壞處
好處
①保護函數內的變量安全 ,實現封裝,防止變量流入其餘環境發生命名衝突
②在內存中維持一個變量,能夠作緩存(但使用多了同時也是一項缺點,消耗內存)
③匿名自執行函數能夠減小內存消耗
壞處
①其中一點上面已經有體現了,就是被引用的私有變量不能被銷燬,增大了內存消耗,形成內存泄漏,解決方法是能夠在使用完變量後手動爲它賦值爲null;