iT邦幫忙

0

JS Closure 閉包

閉包

Closures are Functions with Preserved data

  • 將變數 "保存" 在 Function 之內
  • 它產生了一個 context 執行環境,建立 VO 來紀錄 上一層宣告的變數
    而藉由該 VO 達到 內部函式取得外部函式變數的效果。

閉包的用途

  1. 使變數一直保存在記憶體,不會被GC回收 (過多則不佳)
  2. 避免汙染全域 (現有let可用, var需要以function來避免全域汙染)
  3. 創建函式工廠 (下有範例)
  4. 私有化變數 & 方法 (Public & Private 實作)

Garbage Collection 機制

當一個對象不在被引用時,就會被 GC 回收,
反之則 該對象一直保存在 記憶體當中。

  • 由於閉包不會被GC,無需使用時,盡量避免以免影響效能。

範例證明 count 變數會被保存

  function A() {
    var count = 0
    function B() {
      count++
      console.log(count)
    }
    return B
  }
  var C = A()
  C() // 1
  C() // 2
  C() // 3

範例

  1. 將3當作參數 傳入 passed 變數,並可被 add 取得
  2. addTo function 會回傳 add function
  3. 回傳值為 add function 而非值 (add並未執行)
  4. 查看 console 中 Scopes / Closure 可見 { passed:3 }
  5. 由4.可得知 addTo() 保存了 3的值於passed變數
  // outer func
  var addTo = function (passed) {
    // inner func
    var add = function (inner) { return passed + inner }
    return add
  }
  console.dir(addTo(3)) 

  // 實際應用 (我們可以保存變數於函式內)
  var addThree = addTo(3) 
  var addFive = addTo(5) 

  // 查看 [[Scopes]] Closure 可見 passed 保存了不同的值
  console.dir(addFive) 

  console.log(addThree(3)) // 6
  console.log(addFive(3)) // 8

  // 上面的 addFive(3) 等同於
  // IIFE 執行 內部 add Func 並回傳其值
  // (因addTo回傳function,而addTo()會執行該Func)
  (function add(inner) {
    return 5 + inner
  })(3)

Module Pattern 私有化方法範例

  1. 保存 privateCounter 不被回收
  2. privateCounter 因為 return {} 所以無法被修改
  3. 使用定義的 value 取得值 (類似get/set)
var makeCounter = 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()) // 0
counter1.increment()
counter1.increment()
console.log(counter1.value()) // 2
counter1.decrement()
console.log(counter1.value()) // 1
console.log(counter2.value()) // 0

Revealing Pattern

  • 只公開可操作的介面
  function Book(title) {
    console.log('This will Run')
    this.title = title
    this.private = 'Cant Get!'
    this.public = 'Can Get!'
    this.getSummary = function () { return `${this.title}` }
    return {
      title,
      public: this.public,
      getSummary: this.getSummary,
    }
  }
  const b1 = new Book('Book One')
  console.log(b1.public) // Can Get !
  console.log(b1.private) // undefined

自行撰寫的小範例

const counter = function() {
  let count = 0
  function addOne() { count++ }
  function setFive() { count = 5 }
  return {
    addOne,
    setFive,
    count,
    value() { return count }
  }
}

const countA = counter()
const countB = counter()
countA.addOne() // 執行addOne 無return值
countA.addOne() // 執行addOne 無return值

// 取得 countA物件之下的count (不會被更新!)
console.log(countA.count) // 0
// 取得 countA物件/valueFunc/[[Scope]]/Closure {count:1}
console.log(countA.value()) // 2 (取得count)

countA.setFive()
console.log(countA.value()) // 5 (取得count)
console.log(countB.value()) // 0 (不受影響)

Scope Chain

VO 環境的變數物件,裏面會存該函式宣告的值 Ex:var a = 1;

函式執行環境中 定義了一個 scope chain 屬性,它負責記錄包含自己的 VO + 所有上層執行環境的 VO 的集合。
因此可藉由該屬性,內部函式可以存取外部函式的變數,反之則否。

Lexical Scoping 語彙範疇

代表函式內部定義的程式碼是根據定義時決定其值而不是動態決定。

參考資料

Youtube講解影片
MDN
參透Javascript閉包與Scope Chain


尚未有邦友留言

立即登入留言