說說你對(duì)閉包的理解?閉包使用場(chǎng)景

一、是什么

一個(gè)函數(shù)和對(duì)其周圍狀態(tài)(lexical environment,詞法環(huán)境)的引用捆綁在一起(或者說函數(shù)被引用包圍),這樣的組合就是閉包(closure)

也就是說,閉包讓你可以在一個(gè)內(nèi)層函數(shù)中訪問到其外層函數(shù)的作用域

JavaScript中,每當(dāng)創(chuàng)建一個(gè)函數(shù),閉包就會(huì)在函數(shù)創(chuàng)建的同時(shí)被創(chuàng)建出來,作為函數(shù)內(nèi)部與外部連接起來的一座橋梁

下面給出一個(gè)簡(jiǎn)單的例子

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

displayName() 沒有自己的局部變量。然而,由于閉包的特性,它可以訪問到外部函數(shù)的變量

二、使用場(chǎng)景

任何閉包的使用場(chǎng)景都離不開這兩點(diǎn):

  • 創(chuàng)建私有變量
  • 延長(zhǎng)變量的生命周期

一般函數(shù)的詞法環(huán)境在函數(shù)返回后就被銷毀,但是閉包會(huì)保存對(duì)創(chuàng)建時(shí)所在詞法環(huán)境的引用,即便創(chuàng)建時(shí)所在的執(zhí)行上下文被銷毀,但創(chuàng)建時(shí)所在詞法環(huán)境依然存在,以達(dá)到延長(zhǎng)變量的生命周期的目的

下面舉個(gè)例子:

在頁(yè)面上添加一些可以調(diào)整字號(hào)的按鈕

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

柯里化函數(shù)

柯里化的目的在于避免頻繁調(diào)用具有相同參數(shù)函數(shù)的同時(shí),又能夠輕松的重用

// 假設(shè)我們有一個(gè)求長(zhǎng)方形面積的函數(shù)
function getArea(width, height) {
    return width * height
}
// 如果我們碰到的長(zhǎng)方形的寬老是10
const area1 = getArea(10, 20)
const area2 = getArea(10, 30)
const area3 = getArea(10, 40)

// 我們可以使用閉包柯里化這個(gè)計(jì)算面積的函數(shù)
function getArea(width) {
    return height => {
        return width * height
    }
}

const getTenWidthArea = getArea(10)
// 之后碰到寬度為10的長(zhǎng)方形就可以這樣計(jì)算面積
const area1 = getTenWidthArea(20)

// 而且如果遇到寬度偶爾變化也可以輕松復(fù)用
const getTwentyWidthArea = getArea(20)

使用閉包模擬私有方法

JavaScript中,沒有支持聲明私有變量,但我們可以使用閉包來模擬私有方法

下面舉個(gè)例子:

var Counter = (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 */

上述通過使用閉包來定義公共函數(shù),并令其可以訪問私有函數(shù)和變量,這種方式也叫模塊方式

兩個(gè)計(jì)數(shù)器 Counter1Counter2 是維護(hù)它們各自的獨(dú)立性的,每次調(diào)用其中一個(gè)計(jì)數(shù)器時(shí),通過改變這個(gè)變量的值,會(huì)改變這個(gè)閉包的詞法環(huán)境,不會(huì)影響另一個(gè)閉包中的變量

其他

例如計(jì)數(shù)器、延遲調(diào)用、回調(diào)等閉包的應(yīng)用,其核心思想還是創(chuàng)建私有變量和延長(zhǎng)變量的生命周期

三、注意事項(xiàng)

如果不是某些特定任務(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)造器中。

原因在于每個(gè)對(duì)象的創(chuàng)建,方法都會(huì)被重新賦值

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;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};
?著作權(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)容

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