javascript中的閉包

該文章是MDN閉包文檔學(xué)習(xí)筆記,方便日后查閱。
如要查閱源文檔,請(qǐng)移步閉包文檔傳送門

閉包

閉包是函數(shù)和聲明該函數(shù)的詞法環(huán)境的組合。

一、詞法作用域

嵌套的函數(shù)可以訪問(wèn)在其外部聲明的變量。

function init() {
    var name = "Mozilla"; // name 是一個(gè)被 init 創(chuàng)建的局部變量
    function displayName() { // displayName() 是內(nèi)部函數(shù),一個(gè)閉包
        alert(name); // 使用了父函數(shù)中聲明的變量
    }
    displayName();
}
init();

二、閉包

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc()
  • 在于內(nèi)部函數(shù) displayName() 在執(zhí)行前,被外部函數(shù)返回
  • JavaScript中的函數(shù)會(huì)形成閉包,閉包是由函數(shù)以及創(chuàng)建該函數(shù)的詞法環(huán)境組合而成。這個(gè)環(huán)境包含了這個(gè)閉包創(chuàng)建時(shí)所能訪問(wèn)的所有局部變量
  • myFunc 是執(zhí)行 makeFunc 時(shí)創(chuàng)建的 displayName 函數(shù)實(shí)例的引用,而 displayName 實(shí)例仍可訪問(wèn)其詞法作用域中的變量,即可以訪問(wèn)到 name

再看一個(gè)例子

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
  • makeAdder 是一個(gè)函數(shù)工廠 — 他創(chuàng)建了將指定的值和它的參數(shù)相加求和的函數(shù)
  • add5 和 add10 都是閉包。它們共享相同的函數(shù)定義,但是保存了不同的詞法環(huán)境。

三、閉包的使用

閉包允許將函數(shù)與其所操作的某些數(shù)據(jù)(環(huán)境)關(guān)聯(lián)起來(lái),類似于面向?qū)ο缶幊痰膶?duì)象(有成員也有方法)

1. 作為回調(diào)函數(shù)工廠
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}
var size12 = makeSizer(12);
document.getElementById('size-12').onclick = size12 ;
2. 用閉包模擬私有方法
var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }   
})(); //立即執(zhí)行的匿名函數(shù)

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 */

這個(gè)環(huán)境中包含兩個(gè)私有項(xiàng):名為 privateCounter 的變量和名為 changeBy 的函數(shù)。這兩項(xiàng)都無(wú)法在這個(gè)匿名函數(shù)外部直接訪問(wèn)。必須通過(guò)匿名函數(shù)返回的三個(gè)公共函數(shù)訪問(wèn)。
注意:兩個(gè)計(jì)數(shù)器 counter1 和 counter2 是如何維護(hù)它們各自的獨(dú)立性的。每個(gè)閉包都是引用自己詞法作用域內(nèi)的變量 privateCounter 。每次調(diào)用其中一個(gè)計(jì)數(shù)器時(shí),通過(guò)改變這個(gè)變量的值,會(huì)改變這個(gè)閉包的詞法環(huán)境。然而在一個(gè)閉包內(nèi)對(duì)變量的修改,不會(huì)影響到另外一個(gè)閉包中的變量。

3. 一個(gè)常見錯(cuò)誤:在循環(huán)中創(chuàng)建閉包

可以再循環(huán)中創(chuàng)建閉包,但如果編碼過(guò)于想當(dāng)然,容易犯一個(gè)常見的錯(cuò)誤
在 ECMAScript 2015 引入 let 關(guān)鍵字 之前,在循環(huán)中有一個(gè)常見的閉包創(chuàng)建問(wèn)題。參考下面的示例:

code 1

function showHelp(help) {
  return function() {
    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 = showHelp(item.help);
  }
}

setupHelp();

比較如下代碼

code 2

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();
  • ①處在循環(huán)中創(chuàng)建閉包,三個(gè)共享了同一個(gè)詞法作用域,在這個(gè)作用域中存在一個(gè)變量item。當(dāng)onfocus的回調(diào)執(zhí)行時(shí),item.help的值被決定。由于循環(huán)在事件觸發(fā)之前早已執(zhí)行完畢,變量對(duì)象item(被三個(gè)閉包所共享)已經(jīng)指向了helpText的最后一項(xiàng)。
  • 應(yīng)該使用第一種方式:函數(shù)工廠的方式創(chuàng)建閉包,三個(gè)共享不同的語(yǔ)法作用域

code 3 : 另一種方法使用了匿名閉包

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);
       }
    })(); // 馬上把當(dāng)前循環(huán)項(xiàng)的item與事件回調(diào)相關(guān)聯(lián)起來(lái)
  }
}

setupHelp();

code 4: 避免使用過(guò)多的閉包,可以用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();

這個(gè)例子使用let而不是var,因此每個(gè)閉包都綁定了塊作用域的變量,這意味著不再需要額外的閉包。

四、性能考量

如果不是某些特定任務(wù)需要使用閉包,在其它函數(shù)中創(chuàng)建函數(shù)是不明智的,因?yàn)殚]包在處理速度和內(nèi)存消耗方面對(duì)腳本性能具有負(fù)面影響。
例如,在創(chuàng)建新的對(duì)象或者類時(shí),方法通常應(yīng)該關(guān)聯(lián)于對(duì)象的原型,而不是定義到對(duì)象的構(gòu)造器中。原因是這將導(dǎo)致每次構(gòu)造器被調(diào)用時(shí),方法都會(huì)被重新賦值一次(也就是,每個(gè)對(duì)象的創(chuàng)建)。

考慮以下示例:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

上面的代碼并未利用到閉包的好處,我們可以修改成如下:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

但我們不建議重新定義原型??筛某扇缦吕樱?/p>

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

在前面的兩個(gè)示例中,繼承的原型可以為所有對(duì)象共享,不必在每一次創(chuàng)建對(duì)象時(shí)定義方法。參見 對(duì)象模型的細(xì)節(jié) 一章可以了解更為詳細(xì)的信息。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 閉包沒有想象的那么簡(jiǎn)單 閉包的概念在JavaScript中占據(jù)了十分重要的地位,有不少開發(fā)者分不清匿名函數(shù)和閉包的...
    老馬的春天閱讀 781評(píng)論 2 8
  • 一、前言 對(duì)于 JavaScript 來(lái)說(shuō),閉包是一個(gè)非常強(qiáng)大的特征。但對(duì)于剛開始接觸的初學(xué)者來(lái)說(shuō)它又似乎是特別高...
    東野文然閱讀 1,012評(píng)論 8 18
  • 廢話 閉包,這個(gè)詞甚至讓很多人看到都覺得頭疼,有的人說(shuō)這東西非常好用,有人說(shuō)不懂是啥東西... 引用一段JavaS...
    庸者的救贖閱讀 452評(píng)論 0 3
  • 首先要理解作用域 在JavaScript中,作用域通常是指代碼的上下文(context)。能夠定義全局或者局部作用...
    猿小v閱讀 523評(píng)論 1 12
  • 去年我寫了一篇“closures的簡(jiǎn)介”,它的目的是幫助大家理解‘什么是閉包,閉包是如何工作的’?,F(xiàn)在我嘗試從另外...
    花括弧閱讀 243評(píng)論 0 0

友情鏈接更多精彩內(nèi)容