iT邦幫忙

2023 iThome 鐵人賽

DAY 5
1
Modern Web

那些你可能要知道的前端知識系列 第 5

【day5】(JavaScript) 閉包 Closure

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20230908/20148303WK0XRlo7Aw.png

在JavaScript中,閉包(Closure)與作用域鏈(Scope Chain)的關係非常重要,在往下看之前建議可以先複習一下「【day3】(JavaScript) 作用域 Scope」的文章~


作用域鏈(Scope Chain)

快速複習一下作用域鏈

var globalVar = "哇系全域";

function outerFunction() {
    var outerVar = "哇系外層";

    function innerFunction() {
        var innerVar = "哇系內層";
        console.log(innerVar); // 輸出 "哇係內層"
        console.log(outerVar); // 輸出 "哇係外層"
        console.log(globalVar); // 輸出 "哇係全域"
    }

    innerFunction();
}

outerFunction();

innerFunction中可以訪問到外層宣告的變數,像是outerVarglobalVar,但是在外層的outerFunction訪問不到內層的innerVar

當在自己層級找不到變數時,則會一層一層往外找,直到global為止。
這個行為即是作用域鏈(Scope Chain)


閉包(Closure)是什麼

有了作用域鏈的概念後,再舉例一段程式碼看看什麼是閉包(Closure)

function makeFunc() {
      const name = "名字";
  function b() {
    return name
  }
  return b;
}

const a = makeFunc();
a();
  • 定義一個makeFunc的函式,函式內部有name變數以及內層函式b
  • 內層函式b因為 作用域鏈(Scope Chain) 的關係可以訪問到name變數,並且當b函式被調用時會返回name的值「"名字"」
  • makeFunc函式的返回值是b。(這邊返回的是函式本身,不是函式結果)
  • 定義一個a變數將makeFunc()賦值給它,當我們呼叫a變數時makeFunc()會被調用,然後返回b函式

因此可以簡單整理出閉包(Closure)是什麼
閉包就是內部函式可以透過作用域取得函式外部的變數,並且記住這個變數,因此閉包很常被用來做狀態保存。

謎之音:理論上來看,是不是function就都算是閉包呢?是沒有錯的
對於這個議題更詳盡的探討,推薦Huli的這篇文章「所有的函式都是閉包:談 JS 中的作用域與 Closure


再來寫一個例子~

function counter(){
    let count = 0

    function innerFunc(){
        return ++count
    }

    return innerFunc
    
}

const aCounter = counter()
const bCounter = counter()

console.log(aCounter()) // 輸出 1
console.log(aCounter()) // 輸出 2

console.log(bCounter()) // 輸出 1

透過閉包(Closure)的方式來建立counter函式,內層的innerFunc函式透過(Scope Chain)返回count的值,最後由counter函式返回innerFunc
這樣子也就可以讓其他的外層儲存到裡面的變數,並且aCounterbCounter也不會影響到彼此的值,更不需要擔心count會在其他地方被改變。


閉包不應該使用的情境或需謹慎使用

過度使用造成記憶體問題

function a(){
    const hugeData = newArray(10000).fill("data")
    
    return function b(){
        return hugeData.length
    }
}

const closures = []

for(let i = 0; i < 100; i++){
    return closures.push(a())
}

在這個範例中可以看到每一個closures都對hugeData有參考,即使之後在某些時刻不需要這些資料了,但是它不會被記憶體回收,如果不斷建立這個閉包,會消耗大量的記憶體,可能會讓瀏覽器的產生效能問題。


總結

閉包是 JavaScript 中的一個核心和強大特性,它允許函數記住並訪問其外部作用域的變數,使其在作用域以外的地方也可以被調用。雖然閉包提供了資訊隱藏和數據封裝的能力,但不當使用可能會導致記憶體泄漏和其他問題。因此需要謹慎在適當的情境下使用它ʕง•ᴥ•ʔง

參考資料
所有的函式都是閉包:談 JS 中的作用域與 Closure
JavaScript 進階 - 什麼是閉包?探討 Closure & Scope Chain
MDN


上一篇
【day4】(Javascript) 提升Hoisting
下一篇
【day6】function declaration 、 function expression 差別
系列文
那些你可能要知道的前端知識30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言