iT邦幫忙

2021 iThome 鐵人賽

DAY 14
0
自我挑戰組

JavaScript 奇奇怪怪的核心觀念系列 第 14

(Day14) 閉包 (Closure) 介紹

  • 分享至 

  • xImage
  •  

閉包算是在 JS 中常聽到,卻不容易使用的一個方法,更多狀況是不小心用出來,~~因此出 bug ~~

閉包與記憶體

在介紹閉包之前,先來看看下面範例:

function randomString(length) {
      var result = '';
      var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
      var charactersLength = characters.length;
      for (var i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
      }
      return result;
    }

    function getData() {
      var demoData = [];
      for (let i = 0; i < 1000; i++) {
        demoData.push(randomString(1000))
      }
    }
    getData();

randomString 是一個會根據設定執行數次,產生亂碼字串的方法,而這亂碼字串會藉由 return result 回傳,最後又在 push 至 demoData 變數上。

使用 chrome 無痕模式來觀察 Memory ,可以發現在執行上述程式碼時,記憶體使用了 1.3 MB

再來執行另外一段非常相近的程式碼,來看看他的記憶體使用量:

function randomString(length) {
      var result = '';
      var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
      var charactersLength = characters.length;
      for (var i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
      }
      return result;
    }
    
    var demoData = [];
    function getData() {
      for (let i = 0; i < 1000; i++) {
        demoData.push(randomString(1000))
      }
    }
    getData();

Untitled

可以發現這個範例記憶體增使用量將近 21MB ,這是因為第二個範例 demoData 變數是被放在外層也就是全域( window)底下,而 demoData 變數此時是能在被呼叫、使用的。

而第一個範例中, demoData 是在函示裡頭, demoData 變數則會跟著 getData 執行完畢時,一同被釋放記憶體,此時 demoData 也是無法被呼叫的,因此兩者記憶體落差十分大。

之所以要先講這一段是因為,記憶體可以說是閉包的一個重點。

閉包範例與解釋

接下先來看看閉包的一個簡單範例

function openFn() {
      let num = 10
      function ClosureFn(newNum) {
        num = num + newNum
        return num
      }
      //函示內 return 函示就會變成 『閉包』
      return ClosureFn
    }
const useClosure = openFn()

console.log(useClosure(10)) //20
console.log(useClosure(10)) //30
console.log(useClosure(10)) //40

在上面範例中 ClosureFn 其實就是閉包,若要執行這個閉包可以直接使用 openFn()(100) 其就會被執行,不過一般來說我們不會直接使用兩個 ()() 小刮號做執行,而是像上面範例中使用 openFn() 並且再用一個變數來做指向。

而上面有提到記憶體是閉包的重點,關於這一點我們可以看看連續執行 useClosure() 後回傳的值會不斷疊加,然而 ClosureFn 閉包函示內部雖然沒有 let num = 100 ,不過閉包內部會因為 num = num + newNum 這段程式碼,有使用到 num 變數,因此按照作用域的規則,會訪問(參考)外層函式的 let num 變數,因為這個訪問(參考)的動作,就會讓 num 變數的記憶體『不被釋放』,因此當正是因為這個『不被釋放』,我們使用 useClosure(10) 的值才可以不斷被疊加。

這邊也試者使用圖片來增加對閉包的理解:

之所以要使用閉包,就是因為可以透過不同變數、常數,讓閉包回傳資料各自獨立,某些需要重複使用程式碼的狀況就可以使用閉包,例如

function openFn() {
      let num = 10
      function ClosureFn(newNum) {
        num = num + newNum
        return num
      }
      //函示內 return 函示就會變成 『閉包』
      return ClosureFn
    }
const useClosure1 = openFn()
console.log(useClosure1(10))
console.log(useClosure1(10))
const useClosure2 = openFn()
console.log(useClosure2(100))
console.log(useClosure2(100))

下篇則會介紹閉包延伸運用。

參考文獻


上一篇
(Day13) 函式基礎與參數介紹
下一篇
(Day15) 閉包進階使用,工廠模式及私有方法。
系列文
JavaScript 奇奇怪怪的核心觀念30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言