这些观念从"克服 JS 的奇怪部分"影片中学习的,这系列影片真的是好东西,所谓的闭包(closure),也就是说执行环境可以将那些它所要reference的外部变数给关住、包住,即便执行环境已经消失
,此现象称作闭包(closure),在介绍闭包之前我们先来解释一下外部环境参照(Reference to Outer Environment)、範围链(scope chain)的东西。
外部环境参照
每个执行环境都有一个参照到他的外部环境
,所谓的外部环境参照(Reference to Outer Environment)就是指说当使用变数时,js不只会在当前的执行环境中寻找变数,也会到外部环境参照中寻找变数
,而js会依照物理位置来判断其外部参照环境为何
。
此处函数b及a之物理位置皆在全域环境底下,如下列例子,首先我们全域执行环境中有func b,func a,let myVar,呼叫a函数以后,创建函数a的执行环境,并且他的变数环境有myVar=2,接着,呼叫b函数,其执行环境消失,离开执行堆,但是变数myVar=2并不会消失,而是在记忆体空间保留
,再来就创建函数b的执行环境,做console操作然后执行环境消失,离开执行堆,所以b的外部参照环境就是我们的全域执行环境,而a外部参照环境也是一样。
let myVar = 1; function b() { console.log(myVar); } function a() { let myVar = 2; b(); } a(); // 1
但当我们将在函数a中所宣告的变数myVar给取消,也就是把let拿掉,那我们的变数myVar就会变成全域变数
,此时结果就会变成2,儘管我们将在全域环境所设置的myVar放置在函数a后面,结果也会是一样,我们在a所宣告的全域变数myVar就会将原本的myVar所覆盖
。
let myVar = 1; function b() { console.log(myVar); } function a() { myVar = 2; b(); } // let myVar = 1; a(); // 2
範围链
再来我们解释一下範围链,範围指的就是我所能取到变数的地方,而链是指我外部环境参照的连结,所谓範围链指的就是我们需要某个执行环境内程式码的变数
,如果他在的当前环境下找不到变数,会往外部环境找
,在执行堆中由上往下找,直到全域环境
。
而下列例子,我们将函数b包在函数a中,也就是b的物理位置在函数a中,所以当我们呼叫函数a,就会创建函数b,并且变数环境会创建一个myVar=2(会保留在记忆体空间),而后离开执行堆,并呼叫函数b,而由于函数b之物理位置位于a之中,因此他会认定函数b之外部参照环境为函数a
,而就会获取到未消失的myVar=2,而a的外部参照环境仍为全域环境。
let myVar = 1; function a() { function b() { console.log(myVar); // 2 } let myVar = 2; b(); } a();
闭包
介绍完上述两个观念后,我们直接来介绍闭包并且使用例子,首先我们在全域环境会有两个变数分别为greetEnglish、
greetSpanish,还有一个func makeGreeting,接着逐行执行到greetEnglish呼叫makeGreeting函数,创造其执行环境,并创造变数环境将language="en"给设置进去,而后回传匿名函数,存入greetEnglish指向其函数,然后离开执行堆,记住我们执行环境的记忆体空间仍在(en不会消失)
,再来greetSpanish也是同样的步骤。
此时,我有两个不同执行环境的记忆体位置,而后,我们呼叫greetEnglish,也就是呼叫我们所回传的函数,而后我们在创建一个匿名函数的执行环境并创建其变数环境(firstname="John", lastname="Doe"),而此时外部参考环境会指向一个执行环境,js会知道我们第一个所创建的执行环境并指向它,也就是会获取到"en",而框起来的範围就是我们的闭包
,而greetSpanish的步骤一样,再次强调,每当呼叫一个函数,就会创建他的执行环境,而在里面被创建的函数,就会指向那个执行环境、指向其记忆体空间
。
function makeGreeting(language) { return function (firstname, lastname) { if (language == "en") { console.log("hi" + firstname + "" + lastname); } if (language == "es") { console.log("Hola" + firstname + "" + lastname); } } } let greetEnglish = makeGreeting("en"); let greetSpanish = makeGreeting("es"); greetEnglish("John", "Doe"); greetSpanish("John", "Doe");