JavaScript 閉包

閉包

對於 JavaScript 的初學者來講,閉包的概念和應用均可以算的上是難點。 MDN 的 JavaScript 文檔對閉包的概念給出了準確的定義,也提供了簡單直觀的的實例,是一個很是好的學習材料。 這篇文章將從文檔出發,對閉包的知識點進行一個簡單的梳理。html

閉包是什麼

首先,咱們須要對閉包提供一個準確的定義。 在文檔中,閉包的定義是 'A closure is the combination of a function and the lexical environment within which that function was declared'。這個定義是很拗口的一句話。 詞法環境(lexical environment)這個描述對於初學者來講過於學術和抽象,咱們只須要記住就好。真正理解定義最好方式就是經過實際的代碼。 假設:閉包

function init() {
   var name = "Hello"; // name 是一個被 init 建立的局部變量
   function displayName() { 
       // displayName() 是內部函數,一個閉包
       alert(name); // 使用了父函數中聲明的變量
   }
   displayName(); 
}
init();

經過以上的代碼塊來看,咱們能夠看到閉包實際上指的就是一個’擁有外部環境變量的函數‘。 在上面的例子中函數 displayName 調用了不屬於自己的外部變量 name,無論此 displayName 函數最終是否被返回,實際上由 name 和 displayName 組成的閉包已經造成。app

function init() {
    var name = "Hello"; // name 是一個被 init 建立的局部變量
    function displayName() { 
        // displayName() 是內部函數,一個閉包
        alert(name); // 使用了父函數中聲明的變量
    }
    return displayName(); // 閉包被返回
}
var fun = init();
fun();

咱們再來看一下這一塊的新的代碼,惟一的區別在於這個代碼中 函數init 返回了一個函數 displayName()。也就是返回了一個閉包。經過這個返回的閉包,咱們就能夠訪問這個函數所相關聯的詞法環境或者說數據。原本應該被銷燬的 name 變量保留了下來,並且只能經過調用閉包的方式來訪問,這也就是私有性。函數


閉包的做用

實際上在上一個例子中,咱們已經看到了閉包的做用,閉包能夠用來模擬私有變量和方法。 它讓函數和函數所操做的某些數據(環境)關聯了起來。學習

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

在上面的例子中 咱們能夠看到變量 privateCounter 和 函數 changeBy 做爲下面三個函數共同的詞法環境造成了閉包。 在 makeCounter()執行以後, 本該消失的詞法環境被保留下來,只能經過返回的三個函數進行更改和訪問。這種行爲模擬出了相似 JAVA 類中的私有變量和私有方法。code


在循環中建立閉包:一個常見錯誤;

在 ECMAScript 2015 引入 let 這個關鍵字以前,在循環中有一個常見的閉包建立問題。htm

<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

這段代碼的結果就是,不管你選擇哪個輸入框,helper 信息永遠都會顯示 Your age (you must be over 16)'。 緣由就在於在返回的三個閉包實際上共享了 item 這一個詞法環境,因此 helper 永遠只顯示爲最後 age 的 helper。 這裏就是閉包裏另外一個很重要的知識點,閉包只會捕獲自由變量的引用,因此當 item 指向的helpText值最後變爲 age 時,三個閉包的中的 item 也都變成了 age。 根據這一點咱們能夠將代碼修改以下ip

function setupHelpAnonymous(){
    var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

    for(var i = 0; i < helpText.length; i++){
      var item = helpText[i];
      (function() {
        var item = helpText[i];
        document.getElementById(item.id).onfocus = function() {
          showHelp(item.help);
        }
     })();
    }
  }

  setupHelpAnonymous()

在上面的代碼片斷中 咱們使用了一個 IIFE (當即執行函數表達式) 對 item 這個引用進行了馬上求值。這樣咱們就能獲得想要的結果。而在ES6中的 ’塊級做用域‘ 也能夠解決這個問題。作用域

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    let item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

每一次循環,都有一個新的 item 被建立,三個閉包再也不共享同一個詞法環境;相比匿名閉包的方式,也沒有建立多餘的閉包。rem

相關文章
相關標籤/搜索