中阶魔法 - 闭包 Closure (一)

前情提要

「艾草艾草,你在做什麽?」

艾草:「没特别做什麽呀!」

「艾草艾草,我问你喔!」

艾草:「嗯嗯,问呀,但为什麽都要叫我名字两次呀?」

「透过两次的呼叫希望可以唤醒存在你心中的真善美,能使你成为更棒的存在!」

艾草:「??」

「上次在哪本魔法书看到的呀,好像某个术式要呼叫两次什麽的,实验一下!」

艾草:「啊,你说的应该是闭包吧?这个术式比较复杂,再陪你复习一次原理吧!」


闭包 Closure

以下引用至 MDN:

闭包(Closure)是函式以及该函式被宣告时所在的作用域环境(lexical environment)的组合。

这样可能不理解,来看一段程序码:

像这样函式内 return 函式,且内层函式内并没有 textContent 变数,需要透过范围链找到外层的 textContent 变数来使用,就称为闭包。

function outer(text) {
	let textContent = text; 
  return function inner(name) {
    console.log(`${textContent},${name}`); //"Hello,艾草"
  };
}

闭包的呼叫要使用两个小括号,只使用一个小括号时,会印出整个内层函式,第二个小括号会呼叫被 return 的函式,底下分别为使用一个及两个小括号呼叫的印出结果:

一个小括号:

outer('Hello');

印出结果:

https://ithelp.ithome.com.tw/upload/images/20211009/201390661zvTLE53sb.png


两个小括号:

outer('Hello')('艾草');

印出结果:

https://ithelp.ithome.com.tw/upload/images/20211009/20139066VutJyxeW0o.png


接下来,闭包还可以这样使用:

将变数赋予值为外层函式後,再透过变数呼叫内层函式,一样可以成功印出。

function outer(text) {
	let textContent = text;
  return function inner(name) {
    console.log(`${textContent},${name}`); //"Hello,艾草"
  };
}
let sayHello = outer('Hello');
sayHello('艾草');

为什麽内层函式可以持续取到外层函式的变数呢?

首先,传值、传参考文章内有提到:每宣告一个变数,都会有相对应的记忆体空间存放变数

每个执行环境都会有对应的记忆体空间存放变数、函式,所以当我们执行到程序码 let sayHello = outer('Hello') 时会跑去建立函式 outer 的执行环境,而执行到 let textContent = text 时,该执行环境内会产生记忆体空间来存放 textContent 变数与其值。

执行堆叠会如下图:

https://ithelp.ithome.com.tw/upload/images/20211008/20139066le9fWNEW9y.png

接下来回到全域执行环境执行到 sayHello('艾草') 时,会进行以下几个步骤:

  1. 函式 outer 会离开执行环境
  2. 全域执行环境的记忆体空间会放进 sayHello()sayHello() 会指向函式 inner
  3. 执行堆叠新增函式 inner 的执行环境,并於记忆体空间放置参数 name
  4. 函式 inner 会需要参照外部函式 outer 的变数 textContent

而执行环境为了参考到外部变数 textContent 会将外部变数包覆起来,而这就是闭包。

https://ithelp.ithome.com.tw/upload/images/20211008/20139066Q1Rl3WUqZf.png

JavaScript 会保留我们需要透过范围链参照的外部变数,即使该变数所在执行环境已离开执行堆叠。


总结

  • 闭包呼叫使用两个小括号,第二个小括号会呼叫要回传的内层函式
  • 每个执行环境都会有对应的记忆体空间存放变数、函式
  • 执行环境从执行堆叠离开并不影响范围链参照其记忆体空间

参考文献

JavaScript 全攻略:克服 JS 的奇怪部分(Udemy)
JavaScript 核心篇(六角学院)
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Closures


<<:  DAY24:Broadcast receiver之简介

>>:  Proxmox VE 建立排程备份及制订保留策略

[ Python] bat 开启 python 环境

D: cd pydata call C:\ProgramData\Anaconda3\Scripts...

D-27. 编译直译、动态静态、强型弱型 && Leetcode:Add Digits && Move Zeroes

Ruby是直译语言 程序码要能让电脑读懂,一定会有一个转译过程。 编译(Compiled langu...

Day-16 用类比电视盒来处理落单的主机们

从开始写文至今已经介绍过 HDMI、色差、SCART 和 S-Video 等 4 种端子了、但在序文...

[Day7] 人才配置:合适的人、合适的运用

从资源配置的角度思考 产品经理不一定有人事决定权,但是可以从资源的角度给予建议 这个是一个特别的经历...

小队快跑 - 是夹心饼乾 或是 承上启下

当在公司历经了三年左右的时间,若能在前面文章的概念中不断落实与反覆演练,加上三年的时间应该可以历经起...