1. 程式人生 > 實用技巧 >關於閉包的基本知識

關於閉包的基本知識

1.什麼是閉包

    閉包是指有權訪問另外一個函式作用域中的變數的函式.可以理解為(能夠讀取其他函式內部變數的函式)

    作用:正常函式執行完畢後,裡面宣告的變數被垃圾回收處理掉,但是閉包可以讓作用域裡的 變數,在函式執行完之後依舊保持沒有被垃圾回收處理掉

2.js垃圾回收機制

    概述:

      JS的垃圾回收機制是為了以防記憶體洩漏,記憶體洩漏的含義就是當已經不需要某塊記憶體時這塊記憶體還存在著,垃圾回收機制就是間歇的不定期的尋找到不再使用的變數,並釋放掉它們所指向的記憶體。

    變數的生命週期:

      當一個變數的生命週期結束之後它所指向的記憶體就應該被釋放。JS有兩種變數,全域性變數和在函式中產生的區域性變數。區域性變數的生命週期在函式執行過後就結束了,此時便可將它引用的記憶體釋放(即垃圾回收),但全域性變數生命週期會持續到瀏覽器關閉頁面。

    垃圾回收方式:

      1.標記清除:

        大部分瀏覽器以此方式進行垃圾回收,當變數進入執行環境(函式中宣告變數)的時候,垃圾回收器將其標記為“進入環境”,當變數離開環境的時候(函式執行結束)將其標記為“離開環境”,在離開環境之後還有的變數則是需要被刪除的變數。標記方式不定,可以是某個特殊位的反轉或維護一個列表等。

        垃圾收集器給記憶體中的所有變數都加上標記,然後去掉環境中的變數以及被環境中的變數引用的變數的標記。在此之後再被加上的標記的變數即為需要回收的變數,因為環境中的變數已經無法訪問到這些變數。

      2.引用計數:

        這種方式常常會引起記憶體洩漏,低版本的IE使用這種方式。機制就是跟蹤一個值的引用次數,當宣告一個變數並將一個引用型別賦值給該變數時該值引用次數加1,當這個變數指向其他一個時該值的引用次數便減一。當該值引用次數為0時就會被回收。  

        引起記憶體洩漏的原因是它不能解決迴圈引用的問題

3.閉包的例項

// 建立閉包最常見的方式函式作為返回值
function foo() {
  var name = "kebi";
  return function() {
    console.log(name);
  };
}
var bar = foo();
bar(); //列印kebi    --外部函式訪問內部變數

  實現一個計數器:

var count = 0;

function add() {
  count = count + 1;
  console.log(count);
}
add(); //確實實現了需求
//但是如果需要第二個計數器呢?
//難道要如下這樣寫嗎?
var count1 = 0;

function add1() {
  count1 = count1 + 1;
  console.log(count1);
}
add1(); //確實實現了需求

 當我們需要更多地時候,這樣明顯是不現實的,這裡我們就需要用到閉包

function addCount() {
  var count= 0;
  return function() {
    count = count + 1;
    console.log(count);
  };
}

  解釋一下上邊的過程: addCount() 執行的時候,返回一個函式, 函式是可以建立自己的作用域的, 但是此時返回的這個函式內部需要引用 addCount()作用域下的變數 count,因此這個 count 是不能被銷燬的.接下來需要幾個計數器我們就定義幾個變數就可以,並且他們都不會互相影響,每個函式作用域中還會儲存 count 變數不被銷燬,進行不斷的累加

var fun1 = addCount();
fun1(); //1
fun1(); //2
var fun2 = addCount();
fun2(); //1
fun2(); //2

4.閉包的缺陷  

  閉包會導致記憶體佔用過高,因為變數都沒有釋放記憶體

5.閉包的例項:

  1. for 迴圈中列印

for (var i = 0; i < 4; i++) {
  setTimeout(function() {
    console.log(i);
  }, 300);
}

  上邊打印出來的都是 4, 可能部分人會認為列印的是 0,1,2,3

  原因:js 執行的時候首先會先執行主執行緒,非同步相關的會存到非同步佇列裡,當主執行緒執行完畢開始執行非同步佇列, 主執行緒執行完畢後,此時 i 的值為 4,說以在執行非同步佇列的時候,打印出來的都是 4(這裡需要大家對event loop 有所瞭解(js 的事件迴圈機制))

如何修改使其正常列印:(使用閉包使其正常列印)

//方法一:
for (var i = 0; i < 4; i++) {
  setTimeout(
    (function(i) {
      return function() {
        console.log(i);
      };
    })(i),
    300
  );
}
// 或者
for (var i = 0; i < 4; i++) {
  setTimeout(
    (function() {
      var temp = i;
      return function() {
        console.log(temp);
      };
    })(),
    300
  );
}
//這個是通過自執行函式返回一個函式,然後在呼叫返回的函式去獲取自執行函式內部的變數,此為閉包

//方法發二:
for (var i = 0; i < 4; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i);
    }, 300);
  })(i);
}
// 大部分都認為方法一和方法二都是閉包,我認為方法一是閉包,而方法二是通過建立一個自執行函式,使變數存在這個自執行函式的作用域裡

  2.真實的獲取多個元素並新增點選事件

var op = document.querySelectorAll("p");
for (var j = 0; j < op.length; j++) {
  op[j].onclick = function() {
    alert(j);
  };
}
//alert出來的值是一樣的
// 解決辦法一:
for (var j = 0; j < op.length; j++) {
  (function(j) {
    op[j].onclick = function() {
      alert(j);
    };
  })(j);
}
// 解決辦法二:
for (var j = 0; j < op.length; j++) {
  op[j].onclick = (function(j) {
    return function() {
      alert(j);
    };
  })(j);
}
//解決方法三其實和二類似
for (var j = 0; j < op.length; j++) {
  op[j].onclick = (function() {
    var temp = j;
    return function() {
      alert(j);
    };
  })();
}

  

閉包是指有權訪問另外一個函式作用域中的變數的函式.可以理解為(能夠讀取其他函式內部變數的函式)