JavaScript筆記——閉包

前言

秋招大大小小若干場面試,幾乎都問了這個問題,固然不知道閉包也不能說會js。因此,不要逃避,好好地來梳理一下。javascript

閉包是什麼?

來看看MDN官網上關於閉包的定義:java

A closure is the combination of a function and the lexical environment within which that function was declared.面試

閉包是函數以及函數聲明所在的詞法環境的組合。這個定義有點乾澀,我的認爲能夠結合閉包的實際場景簡單地這麼理解:函數調用結果返回一個子函數,同時該子函數使用了外部函數的局部變量,使得變量保存在內存中,造成了閉包。segmentfault

舉個例子?

舉個最簡單的栗子~設計模式

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

var fn = func();
fn(); // 0
fn(); // 1

func 函數執行返回一個函數,其中調用了func的局部變量count,致使func函數執行完成後,count變量仍保留在內存空間,未被銷燬,造成了閉包。瀏覽器

閉包的做用?

從上一個例子,能夠看到閉包的特色是讀取函數內部局部變量,並將局部變量保存在內存,延長其生命週期。利用這個特色可使用閉包實現如下功能:閉包

  1. 解決相似循環綁定事件的問題
    在實際開發中,常常會遇到須要循環綁定事件的需求,好比上一篇博客的例子,在id爲container的元素中添加5個按鈕,每一個按鈕的文案是相應序號,點擊打印輸出對應序號。
    其中第一個方法很容易錯誤寫成:app

    var container = document.getElementById('container');
    for(var i = 1; i <= 5; i++) {
     var btn = document.createElement('button'),
         text = document.createTextNode(i);
     btn.appendChild(text);
     btn.addEventListener('click', function(){
       console.log(i);
     })
     container.appendChild(btn);
    }

    雖然給不一樣的按鈕分別綁定了事件函數,可是5個函數其實共享了一個變量 i。因爲點擊事件在 js 代碼執行完成以後發生,此時的變量 i 值爲6,因此每一個按鈕點擊打印輸出都是6。
    爲了解決這個問題,咱們能夠修改代碼,給各個點擊事件函數創建獨立的閉包,保持不一樣狀態的i。函數

    var container = document.getElementById('container');
    for(var i = 1; i <= 5; i++) {
     (function(_i) {
       var btn = document.createElement('button'),
           text = document.createTextNode(_i);
       btn.appendChild(text);
       btn.addEventListener('click', function(){
         console.log(_i);
       })
       container.appendChild(btn);
     })(i);
    }

    注:解決這個問題更好的方法是使用 ES6 的 let,聲明塊級的局部變量。 性能

  2. 封裝私有變量

    經典的計數器例子:

    function makeCounter() {
      var value = 0;
      return {
        getValue: function() {
          return value;
        },
        increment: function() {
          value++;
        },
        decrement: function() {
          value--;
        }
      }
    }
    
    var a = makeCounter();
    var b = makeCounter();
    b.increment();
    b.increment();
    b.decrement();
    b.getValue(); // 1
    a.getValue(); // 0
    a.value; // undefined

    每次調用makeCounter函數,環境是不相同的,因此對b進行的increment/decrement操做不會影響a的value屬性。同時,對value屬性,只能經過getValue方法進行訪問,而不能直接經過value屬性進行訪問。

閉包的問題?

使用閉包會將局部變量保持在內存中,因此會佔用大量內存,影響性能。因此在再也不須要使用這些局部變量的時候,應該手動將這些變量設置爲null, 使變量能被回收。

當閉包的做用域中保存一些DOM節點時,較容易出現循環引用,可能會形成內存泄漏。緣由是在IE9如下的瀏覽器中,因爲BOM 和DOM中的對象是使用C++以COM 對象的方式實現的,而COM對象的垃圾收集機制採用的是引用計數策略,當出現循環引用時,會致使對象沒法被回收。固然,一樣能夠經過設置變量爲null解決。

舉例以下:

function func() {
  var element = document.getElementById('test');
  element.onClick = function() {
      console.log(element.id);
  };
}

func 函數爲 element 添加了閉包點擊事件,匿名函數中又對element進行了引用,使得 element 的引用始終不爲0。解決辦法是使用變量保存所需內容,並在退出函數時將 element 置爲 null。

function func() {
  var element = document.getElementById('test'),
      id = element.id;
  element.onClick = function() {
      console.log(id);
  };
  element = null;
}

參考文章

相關文章
相關標籤/搜索