暗通款曲的闭包

在「闭包」这一关,我一直有一种似懂非懂,玄之又玄的感觉。

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 inner()的小括号拿掉。

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

结果回传的是inner()的程序码:


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

再更进一步,在外层用变数outBee来存取flyOut():

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

var outBee = flyOut();
outBee();
//"玉蜂翅膀上有写「我在绝情谷底」"
console.log(outBee())

还记得「切分变数最小的范围是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)。」

这句话直白易懂,是大神才写得出来的!

所以我们可以透过「闭包」的方式,呼叫「函式」内的「函式」,我们可以把变数封装在函式中,避免变数污染全域环境,而且可以重复存取叫用函式及其内部环境。许多框架也是透过这种方式运作的。


<<:  CSS微动画 - 图片不裁切,纯css实现分格淡出

>>:  全端入门Day29_後端程序撰写之一点点的Golang

用 Python 畅玩 Line bot - 30:Line Notify(三)

在上篇中,我们是需要到 Line Notify 登入後的个人介面发行 token,但总不能叫每一个加...

Day 26 Ruby Symbol

在 Ruby 内有符号(Symbol)这个物件,他跟字串的用法蛮像的,但本质上则不一样。 究竟 Sy...

【在 iOS 开发路上的大小事-Day11】透过 CocoaPods 来管理第三方套件

前情提要 一般在开发的时候,有些功能可能自己写不出来,但是网路上已经有别人写好的,那我们只需要将其引...

Day-28 TimePickerDialog

不知道各位有没有使用过麦当劳报报? 在麦当劳报报APP当中,使用者必须设定时段, 使程序在设定的时段...