JavaScript - 認識閉包


Posted by Andy Tsai on 2020-05-10

什麼是閉包?

閉包是即使函式在其範疇之外被調用,也仍能記得並存取其範疇的能力。

聽起來有點抽象,沒關係,我們來看Code

function foo(){
  var a = 2;
  function bar(){
    console.log(a)
  }
  return bar
}

var baz = foo() 
baz() // 2

一般來說,函式執行完後,Javascript引擎的垃圾回收的機制會釋放不再使用的記憶體,

回到上面的code,照垃圾回收機制,foo()執行之後,它整個內層就應該消失了,
那為何baz調用內層的bar時,依然能順利存取到foo()中的變數a呢?

這種情形就是閉包,閉包保住了變數a,讓它不被釋放,
bar()仍保留一個參考指向foo()的內層範疇,那個參考就叫閉包

所以後面即使在範疇之外調用baz(bar),我們仍能夠順利取到變數a

閉包其實常出現在我們的程式中

其實我們的程式中或多或少都有閉包的存在,只是我們還沒學會閉包時,認不出它是閉包

像是這段看起來隨處可見的callback

function wait(message){
  setTimeout(function timer() {
    console.log(`say ${message}`)
  }, 1000)
}
wait('hi');

事實上,如果不是閉包,wait內層早就被釋放了,
是timer()仍然有個參考指向message,wait中的變數才能夠被保存起來,不被釋放,也讓我們的程式能順利運作

只要我們常把函式當成一級函式到處傳遞,就很有可能時常發現閉包的出現。

單獨的IIFE算是閉包嗎?

var a = 2;
(function IIFE(){ 
    console.log(a)
}())

廣義來說算,但嚴格來說不算,
因為a是經由正常的範疇查找動作找到的,並不是透過閉包。

廣義的閉包?
廣義來說所有的函式都是閉包,在JavaScript中只要有函式被建立,閉包就會自然地產生。想了解更多可以看Huli大大的文章
所有的函式都是閉包:談 JS 中的作用域與 Closure

模組

有一些編程模式也運用了閉包的概念,像是模組,
模組模式可以讓我們隱藏私有資訊,並選擇對外公開的API

function CoolModule() {
  var count = 0; 
  var message = 'hello';

  function increment() {
    count += 1
    console.log(count)
  }

  function saySomething() {
    console.log(message);
  }

  return {
    increment: increment,
    saySomething: saySomething,
  };
}


var foo = CoolModule()

foo.increment() // 1
foo.saySomething() // 'hello'

這段code中count和message就是我們的私有資訊,而return的物件回傳值則是我們公開的API

也可以達到資料隔離的效果

var foo = CoolModule()
var foo2 = CoolModule()
foo.increment() // 1
foo.increment() // 2

foo2.increment() // 1

行使模組模式的兩個必要條件

  • 必須有一個外層函式,且它必須至少被調用一次
  • 外層函式必須至少回傳一個內層函式,以形成閉包,因此得以存取/修改私有變數

Reference:
You Don't Know JS: Scope & Closures


#javascript #closure #w3HexSchool







Related Posts

版本控制-GIT基本指令

版本控制-GIT基本指令

邊上班邊跟課的同學很厲害~時間好珍貴啊QQ

邊上班邊跟課的同學很厲害~時間好珍貴啊QQ

Day03 - Razor Pages

Day03 - Razor Pages



Comments