iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 24
1
Modern Web

從0開始的網頁生活!30天從網頁新手到網頁入門系列 第 24

Day24-閉包!closure

本文已搬家到筆者自己的部落格嘍,有興趣的可以點這個連結

前言

今天要來介紹一個非常弔詭的東西: 閉包(closure) ,至所以會有 閉包 這個東西主要是因為 JavaScript 可以把 function 當成是變數傳來傳去,這邊用一個簡單的例子來形容 閉包

function foo2() {
  function foo() {
    console.log('Hello World')
  }
  foo()
}
foo2()    // 輸出 "Hello World"

從上面這個例子應該可以發現 閉包 最大的特性: function 裡面還會有一個 function ,外層與內層的 function 彼此是互相獨立的,所以假如我一定要執行 foo() 就必須要在 foo2() 裡面寫上 foo() ,這樣在呼叫 foo2()foo() 才會跟著一起跑。

假如 閉包 只有這樣的話就真的太遜了,畢竟上面的例子並不會太難理解,而且做的事情也相當雞肋,所以底下要來介紹一些 閉包 的基本應用以及進階應用,廢話不多說馬上開始吧!

閉包基本觀念與應用

在前言提到 閉包 其實就是單純的 function 裡面還會有一個 function ,既然是 function 我們也可以增加一點應用把 function 當作是回傳值,之後在呼叫 function 的時候就是直接執行內層的 function 了,就像下面的例子。

function foo2() {
  console.log('initial')
  function foo(value) {
    return console.log(value)
  } 
  return foo
}
const foo3 = foo2()   // 輸出 "initial"
foo3(123)             // 輸出 123
foo3(456)             // 輸出 456

這邊讀者應該會有一個疑問為甚麼這邊會是 return foo 而不是 return foo() 呢?

這邊用一個簡單的例子來觀念釐清一下。

const foo = () => 10
console.log(foo())    // 輸出?
console.log(foo)      // 輸出?
  • foo()

    會輸出 10 ,這個應該蠻好理解的,通常用變數來接 function 的回傳值都會這樣寫,所以 foo() 會是一個 number

  • foo

    會輸出 () => 10 ,這個就代表 foo 這個 function 自己了,所以 foo 會是一個 function

看完這個簡單的例子讀者們應該懂為甚麼會寫成 return foo 而不是 return foo() 了吧XD

因為我們要回傳的是 function 而不是單純的值而已。

當我們寫了 const foo3 = foo2() 其實就是在輸出 console.log('initial') 後把 foo 這個 function 丟給 foo3 ,因為 return 的是一個 function 而不是一個 number ,所以 foo3 現在也是一個 function 了,這個 foo3 要做的事情就是 foo 要做的事情,也因此底下寫 foo3(123) 其實就是 foo(123) 的意思喔!

閉包進階應用

除了上面提到的基礎應用外, 閉包 也有進階應用,以底下的加法器為例,今天用了 閉包 就不用特意為了2跟5的加法器去客製兩種 function ,直接寫一個 閉包 並透過回傳 function 的方式就可以順利的用一種方法寫很多種加法器出來。

function adder(x) {
  return function(y) {
    return x + y
  }
}
let adder2 = adder(2)
let adder5 = adder(5)
console.log(adder2(10))  // 輸出12
console.log(adder5(10))  // 輸出15

有了上面的基礎觀念在來看上面這個進階應用應該蠻容易的,一樣可以想成 adder2 是一個 function ,因為 adder() 的回傳值是一個 function ,所以 adder(2) 就代表將 x 傳進去內層的 function ,所以 adder(2) 現在變成了一個 永遠加2的加法器adder(2) 也變成以下這樣的程式碼:

function(y) {
  return 2 + y
}

其實 閉包 一點也不難,最難的也就這樣而已,如果以上的觀念都搞懂的話接下來要來介紹 閉包 一個相當經典的例子提供給讀者思考一下可以如何改寫。

閉包經典範例

這邊用一個 閉包 相當經典而且算是蠻進階的應用,以往我們在寫一個計數器通常都會寫的像以下的程式碼。

let counter = 0    // global variable
function count() {
  counter++
}
console.log(counter)

這絕對不是最好的方法,因為 function 在控制的是全域變數而不是區域變數,這對於程式整體而言可能會產生副作用,今天假如讀者們懂上面提的例子,善用 閉包function 回傳就可以非常輕鬆的改寫上面的程式碼,讓一個計數器不會再控制全域變數而是控制區域變數。

寫法可以參考這裡

總結

今天介紹了 閉包 這個蠻奇特的概念,雖然 閉包 簡單來說就是 function 裡面再包一個 function ,所以蠻多程式都有 閉包 的寫法,然而卻很少有程式可以像 JavaScript 一樣搞得讓人很難理解而且還有那麼多不同的應用XD

今天介紹的概念其實蠻不好理解的,所以讀者有任何不懂的地方也歡迎在底下留言區留言,筆者都會一一解答喔!


上一篇
Day23-非同步實作篇 II!Promise
下一篇
Day25-this作用域
系列文
從0開始的網頁生活!30天從網頁新手到網頁入門30

1 則留言

0
deh
iT邦新手 1 級 ‧ 2019-12-30 13:02:52

十分淺顯易懂!/images/emoticon/emoticon08.gif

我要留言

立即登入留言