iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
自我挑戰組

JavaScript老學徒筆記—馬步篇系列 第 32

暗通款曲的閉包

在「閉包」這一關,我一直有一種似懂非懂,玄之又玄的感覺。

MDN上對「閉包」的定義:

「閉包為函式的組合、還有該宣告函式的作用域環境。這個環境包含閉包建立時,所有位於該作用域的區域變數。」

每個字都看得懂,但是合起來是甚麼意思?

唉!我們重新來看一下函式的寫法:

小龍女在絕情谷底養的玉峰,飛到周伯通住的百花谷,要如何分辨一班的蜜蜂與小龍女養的玉峰呢?當然是看看翅膀上有沒有寫:「我在絕情谷底」,有寫的就是玉蜂。

這是一般函式的寫法:

var bee="蜜蜂";
function flyOut(){
  var bee = "玉蜂";
  return `${bee}翅膀上有寫「我在絕情谷底」`;
} 
flyOut();
//"玉蜂翅膀上有寫「我在絕情谷底」"

如果我們在flyout()再內嵌一個inner函式,這時直接呼叫flyout(),出來的結果會是undefined。

var bee="蜜蜂";
function flyOut(){
  var bee = "玉蜂";
  function inner(){
    return `${bee}翅膀上有寫「我在絕情谷底」`;
  }  
}
flyOut();
//undefined 沒有回傳值

但是如果我們再flyout那一層,加上「return inner();」,會回傳「"玉蜂翅膀上有寫「我在絕情谷底」"」。

var bee="蜜蜂";
function flyOut(){
  var bee = "玉蜂";
  function inner(){
    return `${bee}翅膀上有寫「我在絕情谷底」`;
  }
  return inner();
}
flyOut();
//"玉蜂翅膀上有寫「我在絕情谷底」"

再來把return inside()的小括號拿掉。

var bee="蜜蜂";
function flyOut(){
  var bee = "玉蜂";
  function inner(){
    return `${bee}翅膀上有寫「我在絕情谷底」`;
  }
  return inner;
}
flyOut();
//ƒ inner(){
    return `${bee}翅膀上有寫「我在絕情谷底」`;
  }

結果回傳的是inside()的程式碼:


ƒ inner(){
    return `${bee}翅膀上有寫「我在絕情谷底」`;
  }

再更進一步,在外層用變數outBee來存取flyOut():

var bee="蜜蜂";
function flyOut(){
  var bee = "玉蜂";
  function inner(){
    return `${bee}翅膀上有寫「我在絕情谷底」`;
  }
  return inner;
}

var outBee = flyOut();
flyBee();
//"玉蜂翅膀上有寫「我在絕情谷底」"
var honeyBee = flyout();
//"玉蜂翅膀上有寫「我在絕情谷底」"

還記得「切分變數最小的範圍是function」這句話嗎?

inner()被內嵌在flyOut()之內,所以inner()裡的變數能夠存取的範圍就是flyOut()跟全域的範圍,它在flyOut()裡面找到了bee = "玉蜂",就不會再往外層去找。這種訪問機制就是「作用域鍊(Scope chain)」

JavaScript 引擎的回收機制會釋放不再使用的記憶體,清空不再使用的變數,但閉包為了保留函式記得和存取其執行環境的能力,就會予以保留,不做記憶體回收。所以當程式執行完var outBee = flyOut();這一行,原本應該被記憶體釋放掉的flyOut()裡面的變數bee變成了「自由變數」,還是可以拿來運算。

這種可以適用自由變數的函式,就是「閉包」。

雖然 outBee 位於 flyOut()函式所定義的範疇之外,但由於閉包的緣故, 所以能正常執行inner()函式,並存取到 bee 的值,進而執行出"玉蜂翅膀上有寫「我在絕情谷底」"的結果。

所以在flyout()的函示內部回傳inner函式的同時,除了傳回程式碼之外,也回傳了內部函式建立時的變數值bee="玉蜂",連同執行環境一起被回傳了。

嗯!這樣在絕情谷外的楊過就可以依據蜜蜂身上的資訊找到小龍女了!XD

這就是一種「閉包」的資料結構,包含函式及函式被建立時的當下環境。

許國政先生在《0 陷阱!0 誤解!8 天重新認識 JavaScript!》一書中:

「當你在呼叫函式的以前,範圍鍊就已經被建立了。因此我們可以在函式(outer)裡面「回傳」另一個內部函式(inner)給外層的範圍,使得外層也可以透過『範圍鍊』取得內部的變數(msg)。」

這句話直白易懂,是大神才寫得出來的!

所以我們可以透過「閉包」的方式,呼叫「函式」內的「函式」,我們可以把變數封裝在函式中,避免變數汙染全域環境,而且可以重複存取叫用函式及其內部環境。許多框架也是透過這種方式運作的。


上一篇
IIFE立即執行函式
下一篇
「this」好七怪!
系列文
JavaScript老學徒筆記—馬步篇35

尚未有邦友留言

立即登入留言