JavaScript 閉包

JavaScript 閉包

先來看一下各個不同地方的閉包說法

  1. 你不知道的JavaScript: 當函數可以記住並訪問所在的詞法作用域時及形成閉包,即使不是在該函數的詞法作用域內
  2. JavaScript高级程序设计: 閉包是指有權訪問令一個函數作用域變量的函數
  3. MDN: 閉包(Closure)是函式以及該函式被宣告時所在的作用域環境(lexical environment)的組合。

我自己的理解

如果我們把函數作用域想像成一個書包(從外面是看不到裡面的內容的),那閉包就很像一個超一流扒手,可以偷窺到你書包的東西。

先來個情境

聲明一個背包函數

1
2
3
4
5
6
7
8
function package() {
let pencilCase = {
'原子筆': 2,
'橡皮擦': 1,
'水壺': 1
}
console.log(pencilCase);
}

外界的人看起來會長怎樣(那整個黃黃的一塊講的艱澀一點就是package函數的詞法作用域)

假設今天有一個扒手函數可以調用到鉛筆盒那就形成閉包(如同先前定義的)

附註: 這裡我對package裡面改寫成這樣:

1
2
3
4
5
6
7
8
9
10
11
12
function package() {
let pencilCase = {
'原子筆': 2,
'橡皮擦': 1,
'水壺': 1
}
return {
getPencilCase() {
console.log(pencilCase);
}
}
}

之後扒手開扒

1
2
let pickpocket = package() // 通常函數執行完該作用域的東西就該銷毀了
pickpocket.getPencilCase()


可以發現確實成功獲得書包裡面的鉛筆盒!!!!

其實這跟棧內存有關

擷取

https://juejin.im/post/6844904099771580423

棧內存銷毀:

  • 全局棧內存:關掉頁面的時候才會銷毀
  • 私有棧內存:
    • 1.一般情況下,函數只要執行完成,形成的私有棧內存就會被銷毀釋放掉(排除出現無限極遞歸、出現死循環的模式)
    • 2.但是一旦棧內存中的某個東西(一般都是堆地址)被私有作用域以外的事物給佔用了,則當前私有棧內存不能立即被釋放銷毀(特點:私有作用域中的私有變量等信息也保留下來了=>這種函數執行形成不能被釋放的私有棧內存,也叫做閉包)

從上面兩個可以得出函數作用域裡面確實不會被立即銷毀

到此我們可以知道

只要外面有任何事物佔據了私有作用域裡面的東西就會產生閉包(保存該函數作用域)

所以其實我們要產生閉包的核心觀念是要讓外界竊聽到函數內部,這才是為何我們在函數內要return函數的原因(讓外界卡住內存)的原因

所以下面講幾個例子吧!

例子

範例一 最基本閉包

1
2
3
4
5
6
7
8
9
10
function foo(num) {
function baz() {
console.log(++num);
}
return baz
}

let bar = foo(5) // bar卡住了內存
bar(4) // 這個4其實啥意義都沒有
bar(5) // 這個5也沒啥意義

改一下範例一看會長怎樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo(num) {
function baz() {
console.log(++num);
}
return baz
}

let bar1 = foo(1)
let bar2 = foo(10)

bar1()
bar1()

bar2()
bar2()

可以發現確實可以訪問到num,且我們這邊可以發現會產生不同的值,代表這兩個都會儲存內存,因此我們要小心使用,不然可能會一直浪費內存

還可以這樣

1
2
3
4
5
6
7
8
9
function foo(num) {
spy = function () {
console.log(++num);
}
}
foo(5)
spy()
spy()
spy()

或是這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo(a) {
let bar ={
geta() {
console.log(a++);
}
}
return bar
}

let baz1 = foo(2)
baz1.geta()
baz1.geta()
let baz2 = foo(5)
baz2.geta()
baz2.geta()