溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JavaScript閉包原理與用法的詳細解析

發(fā)布時間:2020-07-20 16:15:14 來源:億速云 閱讀:157 作者:小豬 欄目:web開發(fā)

這篇文章主要講解了JavaScript閉包原理與用法的詳細解析,內(nèi)容清晰明了,對此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會有幫助。

本文實例講述了JavaScript閉包原理與用法。分享給大家供大家參考,具體如下:

閉包(Closure)

閉包是一個函數(shù)和詞法環(huán)境的組合,函數(shù)聲明在這個詞法環(huán)境中。

  • 詞法作用域:

看下面的一個例子:

 
  function init() {
    var name = 'GaoPian';
    // name是局部變量
    function displayName() {
      //displayName();是內(nèi)部函數(shù),一個閉包
      alert(name); // 使用外部函數(shù)聲明的變量
    }
 
    displayName();
  }
  init();

init()創(chuàng)建了一個局部變量name和一個函數(shù)displayName()。

函數(shù)displayName()是一個已經(jīng)定義在init()內(nèi)部的函數(shù),并且只能在函數(shù)init()里面才能訪問得到。

函數(shù)displayName()沒有自己的局部變量,但由于內(nèi)部函數(shù)可以訪問外部函數(shù)變量,displayName()可以訪問到聲明在外部函數(shù)init()的變量name,如果局部變量還存在的話,displayName()也可以訪問他們。

  • 閉包

看下面一個例子

  function makeFunc() {
    debugger
    var name = 'GaoPian';
 
    function displayName() {
      alert(name);
    }
 
    return displayName;
  }
  var myFunc = makeFunc();
  myFunc();

運行這段代碼和之前init()的方法的效果是一樣。

經(jīng)過debugger一遍之后發(fā)現(xiàn):

二者不同之處是,displayName()在執(zhí)行之前,這個內(nèi)部方法是從外部方法返回來的。   

首先,代碼還是會正確運行,在一些編程語言當中,一個函數(shù)內(nèi)的局部變量只存在于該函數(shù)的執(zhí)行期間,隨后會被銷毀,一旦makeFunc()函數(shù)執(zhí)行完畢的話,變量名就不能夠被獲取,但是,由于代碼仍然正常執(zhí)行,這顯然在JS里是不會這樣的。這是因為函數(shù)在JS里是以閉包的形式出現(xiàn)的。

閉包是一個函數(shù)和詞法作環(huán)境的組合,詞法環(huán)境是函數(shù)被聲明的那個作用域,這個執(zhí)行環(huán)境包括了創(chuàng)建閉包時同一創(chuàng)建的任意變量,即創(chuàng)建的這個函數(shù)和這些變量處于同一個作用域當中。在這個例子當中,myFunc()是displayName()的函數(shù)實例,makeFunc創(chuàng)建的時候,displayName隨之也創(chuàng)建了。displayName的實例可以獲得詞法作用域的引用,在這個詞法作用域當中,存在變量name,對于這一點,當myFunc調(diào)用的話,變量name,仍然可以被調(diào)用,因此,變量'GaoPian'傳遞給了alert函數(shù)。

這里還有一個例子

  function makeAdder(x) {
    return function (y) {
      return x + y;
    }
  }
  var add5 = makeAdder(5);
  var add10 = makeAdder(10);
  console.log(add5(2)); // 7
  console.log(add10(2)); // 12

在這個例子當中,我們定義了一個函數(shù)makeAdder(x),傳遞一個參數(shù)x,并且返回一個函數(shù),這個返回函數(shù)接收一個參數(shù)y,并返回x和y的和。   

實際上,makeAdder是一個工廠模式:它創(chuàng)建了一個函數(shù),這個函數(shù)可以計算特定值的和。在上面這個例子當中,我們使用工廠模式來創(chuàng)建新的函數(shù), 一個與5進行加法運算——add5,一個與10進行加法運算——add10。add5和add10都是閉包,他們共享相同的函數(shù)定義,但卻存儲著不同的詞法環(huán)境,在add5的詞法環(huán)境當中,x為5;在add10的詞法環(huán)境當中,x變成了10。

  • 閉包的實踐

閉包是很有用的,因為他讓我們把一些數(shù)據(jù)(詞法環(huán)境)和一些能夠獲取這些數(shù)據(jù)的函數(shù)聯(lián)系起來,這有點和面向?qū)ο缶幊填愃?,在面向?qū)ο缶幊坍斨?,對象讓我們可以把一些?shù)據(jù)(對象的屬性)和一個或多個方法聯(lián)系起來。

因此,你能夠像對象的方法一樣隨時使用閉包。實際上,大多數(shù)的前端JS代碼都是事件驅(qū)動性的:我們定義一些事件,當這個事件被用戶所觸發(fā)的時候(例如用戶的點擊事件和鍵盤事件),我們的事件通常會帶上一個回調(diào):即事件觸發(fā)所執(zhí)行的函數(shù)。舉個栗子,假設(shè)我們希望在頁面上添加一些按鈕,這些按鈕能夠調(diào)整文字的大小,實現(xiàn)這個功能的方式是確定body的字體大小,然后再設(shè)置頁面上其他元素(例如標題)的字體大小,我們使用em作為單位。

  <style>
    body {
      font-family: Helvetica, Arial, sans-serif;
      font-size: 12px;
    }
 
    h2 {
      font-size: 1.5em;
    }
 
    h3 {
      font-size: 1.2em;
    }
  </style>

我們設(shè)置的調(diào)節(jié)字體大小的按鈕能夠改變body的font-size,并且這個調(diào)節(jié)能夠通過相對字體單位,反應(yīng)到其他元素上,

  function makeSizer(size) {
    return function () {
      document.body.style.fontSize = size + 'px';
    };
  }
  var size12 = makeSizer(12);
  var size14 = makeSizer(14);
  var size16 = makeSizer(16);

size12,size14,size16是三個分別把字體大小調(diào)整為12,14,16的函數(shù),我們可以把他們綁定在按鈕上。

<button id="size-12">12</button>
<button id="size-14">14</button>
<button id="size-16">16</button>
document.getElementById('size-12').onclick = size12; 
document.getElementById('size-14').onclick = size14; 
document.getElementById('size-16').onclick = size16;

通過閉包來封裝私有方法:類似JAVA語言能夠聲明私有方法,意味著只能夠在相同的類里面被調(diào)用,JS無法做到這一點,但卻可以通過閉包來封裝私有方法。私有方法不限制代碼:他們提供了管理命名空間的一種強有力方式。

下面代碼闡述了怎樣使用閉包來定義公有函數(shù),公有函數(shù)能夠訪問私有方法和屬性。

  var counter = (function () {
    debugger;
    var privateCounter = 0;
 
    function changeBy(val) {
      privateCounter += val;
    }
 
    return {
      increment: function () {
        changeBy(1);
      },
      decrement: function () {
        changeBy(-1);
      },
      value: function () {
        return privateCounter;
      }
    };
  })();
  console.log(counter.value());// 0
  counter.increment();
  counter.increment();
  console.log(counter.value());// 2
  counter.decrement();
  console.log(counter.value()); // 1

在之前的例子當中,每個閉包具有他們自己的詞法環(huán)境,而在這個例子中,我們創(chuàng)建了一個單獨的詞法環(huán)境,這個詞法環(huán)境被3個函數(shù)所共享,這三個函數(shù)是counter.increment, counter.decrement和counter.value。

共享的詞法環(huán)境是由匿名函數(shù)創(chuàng)建的,一定義就可以被執(zhí)行,詞法環(huán)境包含兩項:變量privateCounter和函數(shù)changeBy,這些私有方法和屬性不能夠被外面訪問到,然而,他們能夠被返回的公共函數(shù)訪問到。這三個公有函數(shù)就是閉包,共享相同的環(huán)境,JS的詞法作用域的好處就是他們可以互相訪問變量privateCounter和changeBy函數(shù)。

下面一個例子:

  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();
  alert(counter1.value());
  /* Alerts 0 */
  counter1.increment();
  counter1.increment();
  alert(counter1.value());
  /* Alerts 2 */
  counter1.decrement();
  alert(counter1.value());
  /* Alerts 1 */
  alert(counter2.value());
  /* Alerts 0 */

兩個計數(shù)器counter1和counter2分別是互相獨立的,每個閉包具有不同版本的privateCounter,每次計數(shù)器被調(diào)用,詞法環(huán)境會改變變量的值,但是一個閉包里變量值的改變并不影響另一個閉包里的變量。

  • 循環(huán)中創(chuàng)建閉包:常見錯誤

看下面一個例子:

<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();

helpText 數(shù)組定義了三個有用的hint,每個分別與輸入框的id相對應(yīng),每個方法與onfocus事件綁定起來。當你運行這段代碼的時候,不會像預(yù)期的那樣工作,不管你聚焦在哪個輸入框,始終顯示你的age信息。

原因在于,分配給onfocus事件的函數(shù)是閉包,他們由函數(shù)定義構(gòu)成,從setupHelp函數(shù)的函數(shù)作用域獲取。三個閉包由循環(huán)所創(chuàng)建,每個閉包具有同一個詞法環(huán)境,環(huán)境中包含一個變量item.help,當onfocus的回調(diào)執(zhí)行時,item.help的值也隨之確定,循環(huán)已經(jīng)執(zhí)行完畢,item對象已經(jīng)指向了helpText列表的最后一項。

解決這個問題的方法是使用更多的閉包,具體點就是提前使用一個封裝好的函數(shù):

  function showHelp(help) {
    document.getElementById('help').innerHTML = help;
  }
  function makeHelpCallback(help) {
    return function () {
      showHelp(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 = makeHelpCallback(item.help);
    }
  }
  setupHelp();

上面代碼運行正常,回調(diào)此時不共享一個詞法環(huán)境,makeHelpCallback函數(shù)給每個回調(diào)創(chuàng)造了一個詞法環(huán)境,詞法環(huán)境中的help指helpText數(shù)組中對應(yīng)的字符串,使用匿名閉包來重寫的例子如下:

  1. 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++) {      (function () {        var item = helpText[i];        document.getElementById(item.id).onfocus = function () {          showHelp(item.help);        }      })();      // Immediate event listener attachment with the current value of item (preserved until iteration).    }  }  setupHelp();

如果不想使用閉包,也可以使用ES6的let關(guān)鍵字:

  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();

這個例子使用let代替var,所以,每個閉包綁定了塊級作用域,也就意味著不需要額外的閉包。

看完上述內(nèi)容,是不是對JavaScript閉包原理與用法的詳細解析有進一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI