深入理解js中的閉包

2021-09-11 22:38:39 字數 3127 閱讀 3098

寫在前邊:

我們知道,當函式執行時,會形成自己的執行期上下文,並把它掛到自己的作用域鏈上,當函式執行完之後,它的執行期上下文就會被釋放。所以,一般情況下,在函式外部訪問函式內部的變數需要特殊的方法才能解決,這個特殊的方法就是閉包。

在理解閉包前,我建議你先了解下js的作用域。

理解js中的作用域

閉包的概念

閉包:閉包指的是在函式的外部可以訪問函式內部的變數。函式沒有被釋放,整條作用域鏈上的區域性變數都將得到保留。建立閉包的一般方法是在函式內部返回乙個新函式。

通俗的理解:

閉包:顧名思義,是乙個封閉的包,但是這個包露出內部的一條線,這條線就是閉包內部返回函式的作用域鏈,它上面掛載了這個函式以及他的所有父級函式的變數,我們可以通過這條線訪問到函式內的變數,這就是閉包。

閉包的形成機制

我們知道:

當乙個函式被定義時,它的scope屬性會指向他的父級的scope的引用

當乙個函式執行時,會形成它自己的執行期上下文(ao),並把它掛載到他的作用域(scope chain)的最頂端,它的父級的scope依次下移。

當函式執行完畢後,他自己的執行期上下文(ao)會被銷毀

關於scope、ao詳情見理解js中的作用域

這樣,當我們在函式內部返回乙個函式並在其外部被乙個變數接收時,它的作用域鏈上存的是它的父級的作用域鏈,只要這個函式存在則它的作用域鏈就會一直存在,這樣它的作用域鏈上的變數得不到釋放,即能在函式外部訪問作用域內部的變數。

為了便於理解,我們舉個簡單的例子:

function test()

return b

}var global = 100

var c = test()

c() // 101

c() // 102

1.當定義並執行test函式時,它的作用域鏈指向它的ao以及全域性的go

2.當函式b被返回時,b的作用域鏈上掛載這它的父級的作用域鏈,即test執行時的作用域鏈

3.此時,雖然test()函式執行完畢,它的執行期上下文被銷毀(注意這裡的銷毀指的是指向被銷毀,即圖中箭頭消失)。但是此時返回的函式b的scope上擁有b的父級的所有作用域鏈。此時,又將return 的b 賦值給c。所以,當執行c函式時,它會在b的作用域鏈上尋找所需要的變數,這樣就實現了閉包。所以當c執行兩次時,結果分別是101、102

閉包的作用

閉包的作用一般有兩個:

1.可以在函式外部,使用用函式內部的變數

2.函式內部的變數不會被釋放。

閉包的缺陷

由於閉包會使得函式中的變數都被儲存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,

關於閉包的常見面試題

第一題:

var data = ;

for (var i = 0; i < 3; i++) ;

}data[0]();

data[1]();

data[2]();

輸出結果都是3。為什麼?

1.當執行完for迴圈後,此時的全域性執行期上下文為

go:
2.當執行data[0]時,它產生它自己的執行期上下文(ao),此時他的作用域鏈為

scope:[ao,go]
此時,它的ao上沒有i變數,就向它的上一級的執行期上下文中找,即以上的go,所以輸出結果為3

其他兩個執行結果同理。

當我們將其修改為閉包時,即如下**

var data = ;

for (var i = 0; i < 3; i++)

})(i)

}data[0](); // 0

data[1](); // 1

data[2](); // 2

此時,他的執行結果分別為0,1,2

當執行完for迴圈時,此時的go為

go:
當data[0]執行時,此時他的作用域鏈為

scope:[ao,匿名函式的ao,go]
而此時匿名函式的ao為

ao:
data[0]的ao中沒有變數i,所以它沿著作用域鏈向上尋找,找到匿名函式的ao,即此時i為0.

執行data[1],data[2]同理

第二題

for (var i = 0; i < 5; i++) , 1000);

}console.log(i);

以上這道題是在面試中經常問到的問題,那麼它輸出的是什麼呢?相信大多數朋友都可以知道,最後他的結果為

5,5,5,5,5,5。

只要了解了js的執行機制、以及同步非同步的問題,我們橫容易知道第乙個5是立即輸出,之後的5在1s後同時輸出。

那麼我們將它改造為閉包。

for (var i = 0; i < 5; i++) , 1000);

})(i)

}console.log(i);

它的結果為5,0,1,2,3,4

我們分析下它的作用域。

首先,先定義了5個立即執行函式,然後執行迴圈外部的console,此時的go為,所以先輸出5

1s後5個立即執行函式同時執行,此時定時器內部的i為其外部函式(即立即執行函式)的i,此時i分別為0,1,2,3,4,所以輸出為5,0,1,2,3,4

想要那麼有沒有什麼其它方法來改造呢?答案是有的,es6裡提供了乙個叫let的東西,他會形成塊級作用域。

for (let i = 0; i < 5; i++) , 1000);

}console.log(i);

以上**會報錯,因為最後的i是不存在的,因為let形成了塊級作用域,只在for迴圈內部起作用。

深入理解JS閉包

閉包 closure 是j acript語言的乙個難點,也是它的特色,很多高階應用都要依靠閉包實現。一 變數的作用域 要理解閉包,首先必須理解j ascript特殊的變數作用域。變數的作用域無非就是兩種 全域性變數和區域性變數。j ascript語言的特殊之處,就在於函式內部可以直接讀取全域性變數。...

深入理解js閉包(快速理解)

一 閉包的基本概念 閉包是函式的一種高階應用方式 通過建立乙個不被銷毀的儲存空間,來在函式的外部呼叫和使用函式內部的資料二 如何定義乙個不被銷毀的執行空間 預設情況下,函式執行完畢,函式執行空間中的程式,也會被銷毀 定義不被銷毀的執行空間的方法 1,在函式內部,返回乙個引用資料型別 陣列,物件,函式...

深入理解閉包

閉包的定義 mdn 對閉包的定義 根據上面的例子,舉乙個例子 var str xiaoqi function getname getname 函式可以返回str這個變數,但str即不是getname函式的區域性變數,也不是foo函式的引數,所以str就是自由變數。這樣函式getname就是乙個閉包。...