怎樣理解javascript的閉包機制

閉包是js的一個難點也是它的一個特色,是我們必須掌握的js高級特性,那麼什麼是閉包呢?它又有什麼用呢?

我們都知道,js的作用域分兩種,全局和局部,基於我們所熟悉的作用域鏈相關知識,我們知道在js作用域環境中訪問變量的權利是由內向外的,內部作用域可以獲得當前作用域下的變量並且可以獲得當前包含當前作用域的外層作用域下的變量,反之則不能,也就是說在外層作用域下無法獲取內層作用域下的變量,同樣在不同的函數作用域中也是不能相互訪問彼此變量的,那麼我們想在一個函數內部也有限權訪問另一個函數內部的變量該怎麼辦呢?閉包就是用來解決這一需求的,閉包的本質就是在一個函數內部創建另一個函數

我們首先知道閉包有3個特性:

①函數嵌套函數

②函數內部可以引用函數外部的參數和變量

③參數和變量不會被垃圾回收機制回收

本文我們以閉包兩種的主要形式來學習

①函數作為返回值

怎樣理解javascript的閉包機制

在這段代碼中,a()中的返回值是一個匿名函數,這個函數在a()作用域內部,所以它可以獲取a()作用域下變量name的值,將這個值作為返回值賦給全局作用域下的變量b,實現了在全局變量下獲取到局部變量中的變量的值

再來看一個閉包的經典例子

怎樣理解javascript的閉包機制

一般情況下,在函數fn執行完後,就應該連同它裡面的變量一同被銷燬,但是在這個例子中,匿名函數作為fn的返回值被賦值給了fn1,這時候相當於fn1=function(){var n = 0 ... },並且匿名函數內部引用著fn裡的變量num,所以變量num無法被銷燬,而變量n是每次被調用時新創建的,所以每次fn1執行完後它就把屬於自己的變量連同自己一起銷燬,於是乎最後就剩下孤零零的num,於是這裡就產生了內存消耗的問題

再來看一個經典例子-定時器與閉包

寫一個for循環,讓它按順序打印出當前循環次數

怎樣理解javascript的閉包機制

按照預期它應該依次輸出1 2 3 4 5,而結果它輸出了五次5,這是為什麼呢?原來由於js是單線程的,所以在執行for循環的時候定時器setTimeout被安排到任務隊列中排隊等待執行,而在等待過程中for循環就已經在執行,等到setTimeout可以執行的時候,for循環已經結束,i的值也已經編程5,所以打印出來五個5,那麼我們為了實現預期結果應該怎麼改這段代碼呢?(ps:如果把for循環裡面的var變成let,也能實現預期結果)

怎樣理解javascript的閉包機制

引入閉包來保存變量i,將setTimeout放入立即執行函數中,將for循環中的循環值i作為參數傳遞,100毫秒後同時打印出1 2 3 4 5

那如果我們想實現每隔100毫秒分別依次輸出數字,又該怎麼改呢?

怎樣理解javascript的閉包機制

在這段代碼中,相當於同時啟動3個定時器,i*100是為4個定時器分別設置了不同的時間,同時啟動,但是執行時間不同,每個定時器間隔都是100毫秒,實現了每隔100毫秒就執行一次打印的效果。

②閉包作為參數傳遞

怎樣理解javascript的閉包機制

在這段代碼中,函數fn1作為參數傳入立即執行函數中,在執行到fn2(30)的時候,30作為參數傳入fn1中,這時候if(x>num)中的num取的並不是立即執行函數中的num,而是取創建函數的作用域中的num這裡函數創建的作用域是全局作用域下,所以num取的是全局作用域中的值15,即30>15,打印30

最後總結一下閉包的好處與壞處

好處

①保護函數內的變量安全 ,實現封裝,防止變量流入其他環境發生命名衝突

②在內存中維持一個變量,可以做緩存(但使用多了同時也是一項缺點,消耗內存)

③匿名自執行函數可以減少內存消耗

壞處

①其中一點上面已經有體現了,就是被引用的私有變量不能被銷燬,增大了內存消耗,造成內存洩漏,解決方法是可以在使用完變量後手動為它賦值為null;

②其次由於閉包涉及跨域訪問,所以會導致性能損失,我們可以通過把跨作用域變量存儲在局部變量中,然後直接訪問局部變量,來減輕對執行速度的影響


分享到:


相關文章: