學習JavaScript中的閉包closure應該註意什麼

閉包簡述

Mozilla 上這樣解釋閉包:一個函數和對其周圍狀態(lexical environment,詞法環境) 的引用捆綁在一起(或者說函數被引用包圍),這樣的組合就是閉包(closure)。 也就是說,閉包讓你可以在一個內層函數中訪問到其外層函數的作用域。在 JavaScript 中, 每當創建一個函數, 閉包就會在函數創建的同時被創建出來。 詞法(lexical)一詞指的是,詞法作用域根據源代碼中聲明變量的位置來確定該變量在何處可用。

我對閉包的理解:閉包使得可以模擬私有項,可以使得內部函數可以訪問外部函數的屬性,非必要不用閉包。

1.閉包使得內部函數可以訪問外部函數的屬性(變量或方法)

這有時會帶來便利, 例如有時可以通過在外部函數聲明變量,代替全局變量。 下面是一個設備視口大小改變時,重置 echarts 的例子。

// 設備視口大小改變時,重置 echarts
let timer = null
window.onresize = function () {
  // 簡單的防抖動處理
  if (timer) clearTimeout(timer)
  timer = setTimeout(() => {
    console.log(timer)
    chart.resize()
  }, 500)
}

也可以考慮使用閉包的方式,而不必在聲明全局變量(更大范圍的變量) timer,例如這樣

window.onresize = this.debounce(() => {
  chart.resize()
}, 500)

function debounce (fn, delay = 500) {
  let timer = null
  return (p) => {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn(p)
    }, delay)
  }
}

2.閉包的廣闊應用場景

閉包的廣闊應用場景,體現在你使用隻有一個方法的對象的地方,都可以使用閉包。

因為閉包允許將函數與其所操作的某些數據(環境)關聯起來。這顯然類似於面向對象編程。 在面向對象編程中,對象允許我們將某些數據(對象的屬性)與一個或者多個方法相關聯。

而在日常開發中,符合使用閉包的場景其實很常見,因為使用隻有一個方法的對象的地方,都可以使用閉包, 而使用也並不太麻煩,加上閉包本身就是 javascript 的重要知識點,這些加起來使得閉包具備瞭實用的特征。

但如果你不熟練閉包,有更好的替代方案,也不必非要使用,因為實用好用的東西很多, 閉包隻是選擇之一,為瞭給自己多一種選擇閉包又是要學的。

3.用閉包模擬私有方法

JavaScript 沒有類似 JAVA 那樣的將方法聲明為私有的原生支持,但我們可以使用閉包來模擬私有方法。 私有方法不僅僅有利於限制對代碼的訪問,還提供瞭管理全局命名空間的強大能力, 避免非核心的方法弄亂代碼的公共接口部分。

下面的示例展現瞭如何使用閉包來定義公共函數,並令其可以訪問私有函數和變量。這個方式也稱為模塊模式(module pattern)

window.onload = () => {
  let Counter1 = makeCounter(); // 創建實例1
  let Counter2 = makeCounter(); // 創建實例2

  console.log(Counter1.value()); // value:0
  Counter1.add(); // 調用增加函數,執行加一
  console.log(Counter1.value()); // value:1

  console.log(Counter2.value()); // value:0

  // 註意,實例2的 value 沒有受到實例1的影響,也就是說 Counter1 和 Counter2 各自獨立。
  // 每次調用其中一個計數器時,通過改變這個變量的值,會改變這個閉包的詞法環境。
  // 然而在一個閉包內對變量的修改,不會影響到另外一個閉包中的變量。

  // undefined,Counter1 無法直接訪問私有項 privateNumber
  console.log(Counter1.privateNumber);
  // Counter1.changeBy is not a function,Counter1 無法直接訪問私有項 changeBy
  console.log(Counter1.changeBy(10));

  // 問私有項無法被訪問,這提示我們應關註到以這種方式使用閉包,
  // 提供瞭許多與面向對象編程相關的好處 —— 特別是數據隱藏和封裝。
}

/// 聲明一個模塊:計數器,模塊內部包含瞭兩個模擬的私有項 privateNumber 和 changeBy,
// 並返回一個對象,對象內部包含三個屬性,分別是 add(),reduce(),value()。
let makeCounter = function () {
  let privateNumber = 0;

  function changeBy (val) {
    privateNumber += val;
  }

  return {
    add: function () {
      changeBy(1);
    },
    reduce: function () {
      changeBy(-1);
    },
    value: function () {
      return privateNumber;
    }
  }
};

在這個例子中,包含兩個私有項: 名為 privateCounter 的變量和名為 changeBy 的函數。 這兩項都無法在函數外部直接訪問。必須通過匿名函數返回的三個公共函數訪問。這就是模擬瞭私有特性。

4.從性能角度考慮,非必要不使用閉包

關於閉包的性能,我無深入的理解,也無數據證明,但我認為這挺重要的。

因此,這裡引用一下 Mozilla 的說法:

如果不是某些特定任務需要使用閉包,在其它函數中創建函數是不明智的, 因為閉包在處理速度和內存消耗方面對腳本性能具有負面影響。

例如,在創建新的對象或者類時,方法通常應該關聯於對象的原型,而不是定義到對象的構造器中。 原因是這將導致每次構造器被調用時,方法都會被重新賦值一次(也就是說,對於每個對象的創建,方法都會被重新賦值)。

英文原文:

It is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task, as it will negatively affect script performance both in terms of processing speed and memory consumption.

For instance, when creating a new object/class, methods should normally be associated to the object's prototype rather than defined into the object constructor. The reason is that whenever the constructor is called, the methods would get reassigned (that is, for every object creation).

概括就是一句話,非必要不用閉包。好東西很多閉包隻是之一, 當然閉包作為js的重要知識點,作為可能的解決方案之一,學習是必要的。

到此這篇關於學習JavaScript中的閉包closure應該註意什麼的文章就介紹到這瞭,更多相關JS 閉包closuret內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: