1. 程式人生 > >理解JS閉包的含義

理解JS閉包的含義

道理 出現 怎麽辦 就會 發生 循環 引用 事件 方法

一、什麽是閉包

  閉包就是通過返回一個函數來保留某段作用域的一種方法。通過返回函數把本該消失的作用域保留到這個函數中,並且外界可以通過函數訪問這段作用域下的變量。

  例一:

1 function foo(){
2     var a = "我是foo裏面的a";
3 }
4 foo();

  這段代碼執行後,其實a已經不存在了,因為foo執行完了之後foo的作用域消失了,所以作用域裏面的變量不見了,被銷毀了。在JS中存在自動垃圾回收,對不需要的變量或者沒有使用的作用域進行定期清理。當foo執行完,函數內部的變量a沒有在此作用域下任何地方再被引用,所以a變量的作用域會被JS內置垃圾收集器回收。

  那麽,我們需要用某種方法來獲得foo裏面的a,怎麽辦呢?

  例二:

1 function foo(){
2     var a = "我是foo裏面的a";
3     return a;//將變量a給return出來 
4 }
5 let b = foo();//此時b就獲得foo裏面的a了。

  此時,a是不是就獲得了呢?並沒有,我們得到的只是a的副本,假設我以某種方法修改了a的值,b不會發生任何變化。所以,我們並不是使用的foo內部的a。既然直接返回a不行,那麽我們試試返回一個函數。

  例三:

function foo(c){
    var num = c;
    return
function A(){ num++; return num; } } var b = foo(5);//b = function A b();//6 b();//7

  是不是很奇怪,按道理每次都會返回6呀,怎麽會每次疊加呢。其實不然,我們執行的是函數b(從foo返回出來的),並沒有重新執行foo,所以也就不會每次給num重新賦值5。至於為什麽會變成這種累加的情況呢,這是因為函數foo執行完後,其內部的的A函數裏面對num有引用,所以foo的作用域以及變量a被保留在了函數A中,返回給了b。

  現在,我們就能在外界通過函數b來訪問foo內部的變量num了。這就是閉包,我們通過返回一個函數打通了函數內部與外界的橋梁。

二、閉包的作用

 閉包可以解決的一個典型的問題就是循環綁定的問題,由於var聲明的變量可以穿透作用域,所以如下的代碼會出現問題。

  例四:

 <ul class="list">
      <li class="bloc">1</li>
      <li class="bloc">2</li>
      <li class="bloc">3</li>
      <li class="bloc">4</li>
      <li class="bloc">5</li>
 </ul>
  var ali = document.querySelectorAll(‘.wrap ul li‘)
  for(var i = 0,l = ali.length;i < l;i++){
   ali[i].onclick = function(){
        console.log(i)  //5 5 5 5 5
    }
  }

  執行上面代碼你會發現,當你點擊任意li時,都會打印出5。這不對呀,和我們想的不一樣呀,按道理點擊第幾個 li 就會打印出相應的下標呀。這是因為,我們綁定了五次onclik事件,無論你有沒有觸發後面的的函數,for循環都會執行,當你點擊的時候,for循環已經執行完了,i 的值早已經變成了5。var 聲明的i能夠穿透作用域,每次觸發點擊事件時,函數內部的 i 也都是5。

  那麽,我們是不是應該想辦法把每次循環時的 i 值保存下來呢,就好像每個點擊事件都重新給一個新的 i 值。於是,通過閉包可以保存每次循環的 i 值,請看例子:

1  for(var i = 0,l = ali.length;i < l;i++){
2      ali[i].onclick = (function(j){
3          return function(){
4               console.log(j)5          }
6      })(i)
7 }

  我們通過立即執行函數後返回了一個函數的形式,把每次循環的 i 值通過傳參放到了不同的函數中,每個函數都是一個獨立的作用域,每個函數都有了各自的i值,互相不影響,現在就如我們期望的一樣了,點擊第幾個li打印出第幾個 li 的下標。

  

理解JS閉包的含義